Merge pull request #26 from jedmund/query-params
Implement query params on Collection views
This commit is contained in:
commit
b0643d73b0
17 changed files with 672 additions and 235 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -76,3 +76,4 @@ typings/
|
|||
|
||||
# DS_Store
|
||||
.DS_Store
|
||||
*.tsbuildinfo
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@ const AccountModal = () => {
|
|||
const privateSelect = React.createRef<HTMLInputElement>()
|
||||
|
||||
useEffect(() => {
|
||||
console.log(cookies.user)
|
||||
if (cookies.user) setPicture(cookies.user.picture)
|
||||
if (cookies.user) setLanguage(cookies.user.language)
|
||||
}, [cookies])
|
||||
|
|
|
|||
|
|
@ -9,48 +9,61 @@ import './index.scss'
|
|||
interface Props {
|
||||
children: React.ReactNode
|
||||
scrolled: boolean
|
||||
onFilter: (element?: number, raid?: string, recency?: number) => void
|
||||
element?: number
|
||||
raidSlug?: string
|
||||
recency?: number
|
||||
onFilter: ({element, raidSlug, recency} : { element?: number, raidSlug?: string, recency?: number}) => void
|
||||
}
|
||||
|
||||
const FilterBar = (props: Props) => {
|
||||
// Set up translation
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Set up refs for filter dropdowns
|
||||
const elementSelect = React.createRef<HTMLSelectElement>()
|
||||
const raidSelect = React.createRef<HTMLSelectElement>()
|
||||
const recencySelect = React.createRef<HTMLSelectElement>()
|
||||
|
||||
// Set up classes object for showing shadow on scroll
|
||||
const classes = classNames({
|
||||
'FilterBar': true,
|
||||
'shadow': props.scrolled
|
||||
})
|
||||
|
||||
function selectChanged(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
function elementSelectChanged() {
|
||||
const elementValue = (elementSelect.current) ? parseInt(elementSelect.current.value) : -1
|
||||
const raidValue = (raidSelect.current) ? raidSelect.current.value : ''
|
||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
||||
props.onFilter({ element: elementValue })
|
||||
}
|
||||
|
||||
props.onFilter(elementValue, raidValue, recencyValue)
|
||||
function recencySelectChanged() {
|
||||
const recencyValue = (recencySelect.current) ? parseInt(recencySelect.current.value) : -1
|
||||
props.onFilter({ recency: recencyValue })
|
||||
}
|
||||
|
||||
function raidSelectChanged(slug?: string) {
|
||||
props.onFilter({ raidSlug: slug })
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
{props.children}
|
||||
<select onChange={selectChanged} ref={elementSelect}>
|
||||
<option key={-1} value={-1}>{t('elements.full.all')}</option>
|
||||
<option key={-0} value={0}>{t('elements.full.null')}</option>
|
||||
<option key={1}value={1}>{t('elements.full.wind')}</option>
|
||||
<option key={2}value={2}>{t('elements.full.fire')}</option>
|
||||
<option key={3}value={3}>{t('elements.full.water')}</option>
|
||||
<option key={4}value={4}>{t('elements.full.earth')}</option>
|
||||
<option key={5}value={5}>{t('elements.full.dark')}</option>
|
||||
<option key={6}value={6}>{t('elements.full.light')}</option>
|
||||
<select onChange={elementSelectChanged} ref={elementSelect} value={props.element}>
|
||||
<option data-element="all" key={-1} value={-1}>{t('elements.full.all')}</option>
|
||||
<option data-element="null" key={0} value={0}>{t('elements.full.null')}</option>
|
||||
<option data-element="wind" key={1} value={1}>{t('elements.full.wind')}</option>
|
||||
<option data-element="fire" key={2} value={2}>{t('elements.full.fire')}</option>
|
||||
<option data-element="water" key={3} value={3}>{t('elements.full.water')}</option>
|
||||
<option data-element="earth" key={4} value={4}>{t('elements.full.earth')}</option>
|
||||
<option data-element="dark" key={5} value={5}>{t('elements.full.dark')}</option>
|
||||
<option data-element="light" key={6} value={6}>{t('elements.full.light')}</option>
|
||||
</select>
|
||||
<RaidDropdown
|
||||
allOption={true}
|
||||
onChange={selectChanged}
|
||||
currentRaid={props.raidSlug}
|
||||
showAllRaidsOption={true}
|
||||
onChange={raidSelectChanged}
|
||||
ref={raidSelect}
|
||||
/>
|
||||
<select onChange={selectChanged} ref={recencySelect}>
|
||||
<select onChange={recencySelectChanged} ref={recencySelect}>
|
||||
<option key={-1} value={-1}>{t('recency.all_time')}</option>
|
||||
<option key={86400} value={86400}>{t('recency.last_day')}</option>
|
||||
<option key={604800} value={604800}>{t('recency.last_week')}</option>
|
||||
|
|
|
|||
|
|
@ -117,13 +117,18 @@ const GridRep = (props: Props) => {
|
|||
<h2 className={titleClass} onClick={navigate}>{ (props.name) ? props.name : t('no_title') }</h2>
|
||||
<div className={raidClass}>{ (props.raid) ? props.raid.name[locale] : t('no_raid') }</div>
|
||||
</div>
|
||||
{ (account.authorized && (props.user && account.user && account.user.id !== props.user.id)) ?
|
||||
<Button
|
||||
active={props.favorited}
|
||||
icon="save"
|
||||
type={ButtonType.IconOnly}
|
||||
onClick={sendSaveData}
|
||||
/> : ''}
|
||||
{
|
||||
(account.authorized && (
|
||||
(props.user && account.user && account.user.id !== props.user.id)
|
||||
|| (!props.user)
|
||||
)) ?
|
||||
<Button
|
||||
active={props.favorited}
|
||||
icon="save"
|
||||
type={ButtonType.IconOnly}
|
||||
onClick={sendSaveData} />
|
||||
: ''
|
||||
}
|
||||
</div>
|
||||
<div className="bottom">
|
||||
<div className={userClass}>
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ const HeaderMenu = (props: Props) => {
|
|||
<ul className="Menu auth">
|
||||
<div className="MenuGroup">
|
||||
<li className="MenuItem profile">
|
||||
<Link href={`/${accountCookies.account.username}` || ''}>
|
||||
<Link href={`/${accountCookies.account.username}` || ''} passHref>
|
||||
<div>
|
||||
<span>{accountCookies.account.username}</span>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ const PartyDetails = (props: Props) => {
|
|||
function updateDetails(event: React.ChangeEvent) {
|
||||
const nameValue = nameInput.current?.value
|
||||
const descriptionValue = descriptionInput.current?.value
|
||||
const raid = raids.find(raid => raid.id == raidSelect.current?.value)
|
||||
const raid = raids.find(raid => raid.slug === raidSelect.current?.value)
|
||||
|
||||
props.updateCallback(nameValue, descriptionValue, raid)
|
||||
}
|
||||
|
|
@ -83,8 +83,8 @@ const PartyDetails = (props: Props) => {
|
|||
ref={nameInput}
|
||||
/>
|
||||
<RaidDropdown
|
||||
allOption={false}
|
||||
selected={party.raid?.id || ''}
|
||||
showAllRaidsOption={false}
|
||||
currentRaid={party.raid?.slug || ''}
|
||||
onBlur={updateDetails}
|
||||
ref={raidSelect}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1,36 +1,45 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import { appState } from '~utils/appState'
|
||||
import api from '~utils/api'
|
||||
import { appState } from '~utils/appState'
|
||||
import { raidGroups } from '~utils/raidGroups'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
allOption: boolean
|
||||
selected?: string
|
||||
onChange?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
showAllRaidsOption: boolean
|
||||
currentRaid?: string
|
||||
onChange?: (slug?: string) => void
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
}
|
||||
|
||||
const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFieldSet(props, ref) {
|
||||
// Set up router for locale
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
const locale = (router.locale && ['en', 'ja'].includes(router.locale)) ? router.locale : 'en'
|
||||
const locale = router.locale || 'en'
|
||||
|
||||
const [raids, setRaids] = useState<Raid[][]>()
|
||||
|
||||
const raidGroups = [
|
||||
'Assorted', 'Guild Wars', 'Omega', 'T1 Summons', 'T2 Summons',
|
||||
'Primarchs', 'Nightmare', 'Omega (Impossible)', 'Omega II',
|
||||
'Tier 1 Summons (Impossible)', 'Tier 3 Summons', 'Ennead', 'Malice',
|
||||
'6-Star Raids', 'Six-Dragons', 'Nightmare (Impossible)', 'Arcarum: Replicard Sandbox',
|
||||
'Astral', 'Super Ultimate'
|
||||
]
|
||||
// Set up local states for storing raids
|
||||
const [currentRaid, setCurrentRaid] = useState<Raid>()
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [sortedRaids, setSortedRaids] = useState<Raid[][]>()
|
||||
|
||||
// Organize raids into groups on mount
|
||||
const organizeRaids = useCallback((raids: Raid[]) => {
|
||||
// Set up empty raid for "All raids"
|
||||
const all = {
|
||||
id: '0',
|
||||
name: {
|
||||
en: 'All raids',
|
||||
ja: '全て'
|
||||
},
|
||||
slug: 'all',
|
||||
level: 0,
|
||||
group: 0,
|
||||
element: 0
|
||||
}
|
||||
|
||||
const numGroups = Math.max.apply(Math, raids.map(raid => raid.group))
|
||||
let groupedRaids = []
|
||||
|
||||
|
|
@ -38,55 +47,64 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(function useFiel
|
|||
groupedRaids[i] = raids.filter(raid => raid.group == i)
|
||||
}
|
||||
|
||||
if (props.allOption)
|
||||
groupedRaids[0].unshift({
|
||||
id: '0',
|
||||
name: {
|
||||
en: 'All raids',
|
||||
ja: '全てのマルチ'
|
||||
},
|
||||
level: 0,
|
||||
group: 0,
|
||||
element: 0
|
||||
})
|
||||
|
||||
setRaids(groupedRaids)
|
||||
}, [props.allOption])
|
||||
|
||||
useEffect(() => {
|
||||
function fetchRaids() {
|
||||
api.endpoints.raids.getAll()
|
||||
.then((response) => {
|
||||
const raids = response.data.map((r: any) => r.raid)
|
||||
|
||||
appState.raids = raids
|
||||
organizeRaids(raids)
|
||||
})
|
||||
if (props.showAllRaidsOption) {
|
||||
raids.unshift(all)
|
||||
groupedRaids[0].unshift(all)
|
||||
}
|
||||
|
||||
fetchRaids()
|
||||
setRaids(raids)
|
||||
setSortedRaids(groupedRaids)
|
||||
appState.raids = raids
|
||||
}, [props.showAllRaidsOption])
|
||||
|
||||
// Fetch all raids on mount
|
||||
useEffect(() => {
|
||||
api.endpoints.raids.getAll()
|
||||
.then(response => organizeRaids(response.data.map((r: any) => r.raid)))
|
||||
}, [organizeRaids])
|
||||
|
||||
function raidGroup(index: number) {
|
||||
const options = raids && raids.length > 0 && raids[index].length > 0 &&
|
||||
raids[index].sort((a, b) => a.element - b.element).map((item, i) => {
|
||||
// Set current raid on mount
|
||||
useEffect(() => {
|
||||
if (raids && props.currentRaid) {
|
||||
const raid = raids.find(raid => raid.slug === props.currentRaid)
|
||||
setCurrentRaid(raid)
|
||||
}
|
||||
}, [raids, props.currentRaid])
|
||||
|
||||
// Enable changing select value
|
||||
function handleChange(event: React.ChangeEvent<HTMLSelectElement>) {
|
||||
if (props.onChange) props.onChange(event.target.value)
|
||||
|
||||
if (raids) {
|
||||
const raid = raids.find(raid => raid.slug === event.target.value)
|
||||
setCurrentRaid(raid)
|
||||
}
|
||||
}
|
||||
|
||||
// Render JSX for each raid option, sorted into optgroups
|
||||
function renderRaidGroup(index: number) {
|
||||
const options = sortedRaids && sortedRaids.length > 0 && sortedRaids[index].length > 0 &&
|
||||
sortedRaids[index].sort((a, b) => a.element - b.element).map((item, i) => {
|
||||
return (
|
||||
<option key={i} value={item.id}>{item.name[locale]}</option>
|
||||
<option key={i} value={item.slug}>{item.name[locale]}</option>
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<optgroup key={index} label={raidGroups[index]}>
|
||||
<optgroup key={index} label={raidGroups[index].name[locale]}>
|
||||
{options}
|
||||
</optgroup>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<select key={props.selected} defaultValue={props.selected} onBlur={props.onBlur} onChange={props.onChange} ref={ref}>
|
||||
{ Array.from(Array(raids?.length)).map((x, i) => {
|
||||
return raidGroup(i)
|
||||
})}
|
||||
<select
|
||||
key={currentRaid?.slug}
|
||||
value={currentRaid?.slug}
|
||||
onBlur={props.onBlur}
|
||||
onChange={handleChange}
|
||||
ref={ref}>
|
||||
{ Array.from(Array(sortedRaids?.length)).map((x, i) => renderRaidGroup(i)) }
|
||||
</select>
|
||||
)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@ interface Props {
|
|||
children: React.ReactNode
|
||||
}
|
||||
|
||||
interface KeyNames {
|
||||
[key: string]: {
|
||||
en: string,
|
||||
jp: string
|
||||
}
|
||||
}
|
||||
|
||||
const SummonHovercard = (props: Props) => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
|
|
|
|||
19
package-lock.json
generated
19
package-lock.json
generated
|
|
@ -26,6 +26,7 @@
|
|||
"next": "12.0.8",
|
||||
"next-i18next": "^10.5.0",
|
||||
"next-remote-watch": "^1.0.0",
|
||||
"next-usequerystate": "^1.7.0",
|
||||
"react": "17.0.2",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
|
|
@ -46,7 +47,7 @@
|
|||
"eslint": "8.7.0",
|
||||
"eslint-config-next": "12.0.8",
|
||||
"eslint-plugin-valtio": "^0.4.1",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "^4.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
|
|
@ -6271,6 +6272,16 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/next-usequerystate": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/next-usequerystate/-/next-usequerystate-1.7.0.tgz",
|
||||
"integrity": "sha512-OAR+4zgWCz1P5XnrbCrhTBf/UfJIoj17mXRDB5daUq5yYZyYETWQnJFuqEFB9qEd8KrpjBNF2aSmrQpqVXdLUA==",
|
||||
"peerDependencies": {
|
||||
"next": "*",
|
||||
"react": "*",
|
||||
"react-dom": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
|
|
@ -12334,6 +12345,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"next-usequerystate": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/next-usequerystate/-/next-usequerystate-1.7.0.tgz",
|
||||
"integrity": "sha512-OAR+4zgWCz1P5XnrbCrhTBf/UfJIoj17mXRDB5daUq5yYZyYETWQnJFuqEFB9qEd8KrpjBNF2aSmrQpqVXdLUA==",
|
||||
"requires": {}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@
|
|||
"next": "12.0.8",
|
||||
"next-i18next": "^10.5.0",
|
||||
"next-remote-watch": "^1.0.0",
|
||||
"next-usequerystate": "^1.7.0",
|
||||
"react": "17.0.2",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.2",
|
||||
|
|
@ -51,6 +52,6 @@
|
|||
"eslint": "8.7.0",
|
||||
"eslint-config-next": "12.0.8",
|
||||
"eslint-plugin-valtio": "^0.4.1",
|
||||
"typescript": "4.5.5"
|
||||
"typescript": "^4.5.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,97 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import Head from 'next/head'
|
||||
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { queryTypes, useQueryState } from 'next-usequerystate'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
import api from '~utils/api'
|
||||
import { elements, allElement } from '~utils/Element'
|
||||
|
||||
import GridRep from '~components/GridRep'
|
||||
import GridRepCollection from '~components/GridRepCollection'
|
||||
import FilterBar from '~components/FilterBar'
|
||||
|
||||
const emptyUser = {
|
||||
id: '',
|
||||
username: '',
|
||||
granblueId: 0,
|
||||
picture: {
|
||||
picture: '',
|
||||
element: ''
|
||||
},
|
||||
private: false
|
||||
}
|
||||
|
||||
const ProfileRoute: React.FC = () => {
|
||||
// Set up cookies
|
||||
const [cookies] = useCookies(['account'])
|
||||
const headers = (cookies.account) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const { username } = router.query
|
||||
|
||||
// Import translations
|
||||
const { t } = useTranslation('common')
|
||||
const [cookies] = useCookies(['account'])
|
||||
|
||||
// Set up app-specific states
|
||||
const [found, setFound] = useState(false)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [raidsLoading, setRaidsLoading] = useState(true)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [user, setUser] = useState<User>({
|
||||
id: '',
|
||||
username: '',
|
||||
granblueId: 0,
|
||||
picture: {
|
||||
picture: '',
|
||||
element: ''
|
||||
},
|
||||
private: false
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
const [user, setUser] = useState<User>(emptyUser)
|
||||
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
const [element, setElement] = useQueryState("element", {
|
||||
defaultValue: -1,
|
||||
parse: (query: string) => parseElement(query),
|
||||
serialize: value => serializeElement(value)
|
||||
})
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
|
||||
const [recency, setRecency] = useQueryState("recency", queryTypes.integer.withDefault(-1))
|
||||
|
||||
// Filter states
|
||||
const [element, setElement] = useState<number | null>(null)
|
||||
const [raidId, setRaidId] = useState<string | null>(null)
|
||||
const [recencyInSeconds, setRecencyInSeconds] = useState<number | null>(null)
|
||||
// Define transformers for element
|
||||
function parseElement(query: string) {
|
||||
let element: TeamElement | undefined =
|
||||
(query === 'all') ?
|
||||
allElement : elements.find(element => element.name.en.toLowerCase() === query)
|
||||
return (element) ? element.id : -1
|
||||
}
|
||||
|
||||
function serializeElement(value: number | undefined) {
|
||||
let name = ''
|
||||
|
||||
if (value != undefined) {
|
||||
if (value == -1)
|
||||
name = allElement.name.en.toLowerCase()
|
||||
else
|
||||
name = elements[value].name.en.toLowerCase()
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// Add scroll event listener for shadow on FilterBar on mount
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [])
|
||||
|
||||
// Handle errors
|
||||
const handleError = useCallback((error: any) => {
|
||||
if (error.response != null) {
|
||||
console.error(error)
|
||||
|
|
@ -56,24 +103,14 @@ const ProfileRoute: React.FC = () => {
|
|||
const fetchProfile = useCallback(() => {
|
||||
const filters = {
|
||||
params: {
|
||||
element: element,
|
||||
raid: raidId,
|
||||
recency: recencyInSeconds
|
||||
element: (element != -1) ? element : undefined,
|
||||
raid: (raid) ? raid.id : undefined,
|
||||
recency: (recency != -1) ? recency : undefined
|
||||
}
|
||||
}
|
||||
|
||||
const headers = (cookies.account) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
const params = {...filters, ...headers}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
if (username)
|
||||
api.endpoints.users.getOne({ id: username as string, params: params })
|
||||
if (username && !Array.isArray(username))
|
||||
api.endpoints.users.getOne({ id: username , params: {...filters, ...headers} })
|
||||
.then(response => {
|
||||
setUser({
|
||||
id: response.data.user.id,
|
||||
|
|
@ -91,29 +128,55 @@ const ProfileRoute: React.FC = () => {
|
|||
setLoading(false)
|
||||
})
|
||||
.catch(error => handleError(error))
|
||||
}, [username, element, raidId, recencyInSeconds, cookies.account, handleError])
|
||||
}, [element, raid, recency])
|
||||
|
||||
// Fetch all raids on mount, then find the raid in the URL if present
|
||||
useEffect(() => {
|
||||
fetchProfile()
|
||||
}, [fetchProfile])
|
||||
api.endpoints.raids.getAll()
|
||||
.then(response => {
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
|
||||
setRaids(cleanRaids)
|
||||
|
||||
function receiveFilters(element?: number, raid?: string, recency?: number) {
|
||||
if (element != null && element >= 0)
|
||||
setRaidsLoading(false)
|
||||
|
||||
const raid = cleanRaids.find(r => r.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
|
||||
return raid
|
||||
})
|
||||
}, [setRaids])
|
||||
|
||||
// When the element, raid or recency filter changes,
|
||||
// fetch all teams again.
|
||||
useEffect(() => {
|
||||
if (!raidsLoading) fetchProfile()
|
||||
}, [element, raid, recency])
|
||||
|
||||
// On first mount only, disable loading if we are fetching all teams
|
||||
useEffect(() => {
|
||||
if (raidSlug === 'all') {
|
||||
setRaidsLoading(false)
|
||||
fetchProfile()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Receive filters from the filter bar
|
||||
function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) {
|
||||
if (element == 0)
|
||||
setElement(0)
|
||||
else if (element)
|
||||
setElement(element)
|
||||
else
|
||||
setElement(null)
|
||||
|
||||
if (raids && raidSlug) {
|
||||
const raid = raids.find(raid => raid.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
setRaidSlug(raidSlug)
|
||||
}
|
||||
|
||||
if (raid && raid != '0')
|
||||
setRaidId(raid)
|
||||
else
|
||||
setRaidId(null)
|
||||
|
||||
if (recency && recency > 0)
|
||||
setRecencyInSeconds(recency)
|
||||
else
|
||||
setRecencyInSeconds(null)
|
||||
if (recency) setRecency(recency)
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function handleScroll() {
|
||||
if (window.pageYOffset > 90)
|
||||
setScrolled(true)
|
||||
|
|
@ -125,6 +188,8 @@ const ProfileRoute: React.FC = () => {
|
|||
router.push(`/p/${shortcode}`)
|
||||
}
|
||||
|
||||
// TODO: Add save functions
|
||||
|
||||
return (
|
||||
<div id="Profile">
|
||||
<Head>
|
||||
|
|
@ -140,17 +205,22 @@ const ProfileRoute: React.FC = () => {
|
|||
<meta name="twitter:title" content={`@${user.username}\'s Teams`} />
|
||||
<meta name="twitter:description" content={`Browse @${user.username}\''s Teams and filter raid, element or recency`} />
|
||||
</Head>
|
||||
<FilterBar onFilter={receiveFilters} scrolled={scrolled}>
|
||||
<div className="UserInfo">
|
||||
<img
|
||||
alt={user.picture.picture}
|
||||
className={`profile ${user.picture.element}`}
|
||||
srcSet={`/profile/${user.picture.picture}.png,
|
||||
/profile/${user.picture.picture}@2x.png 2x`}
|
||||
src={`/profile/${user.picture.picture}.png`}
|
||||
/>
|
||||
<h1>{user.username}</h1>
|
||||
</div>
|
||||
<FilterBar
|
||||
onFilter={receiveFilters}
|
||||
scrolled={scrolled}
|
||||
element={element}
|
||||
raidSlug={ (raidSlug) ? raidSlug : undefined }
|
||||
recency={recency}>
|
||||
<div className="UserInfo">
|
||||
<img
|
||||
alt={user.picture.picture}
|
||||
className={`profile ${user.picture.element}`}
|
||||
srcSet={`/profile/${user.picture.picture}.png,
|
||||
/profile/${user.picture.picture}@2x.png 2x`}
|
||||
src={`/profile/${user.picture.picture}.png`}
|
||||
/>
|
||||
<h1>{user.username}</h1>
|
||||
</div>
|
||||
</FilterBar>
|
||||
|
||||
<section>
|
||||
|
|
|
|||
152
pages/saved.tsx
152
pages/saved.tsx
|
|
@ -1,43 +1,84 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import Head from 'next/head'
|
||||
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { queryTypes, useQueryState } from 'next-usequerystate'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
|
||||
import api from '~utils/api'
|
||||
import { elements, allElement } from '~utils/Element'
|
||||
|
||||
import GridRep from '~components/GridRep'
|
||||
import GridRepCollection from '~components/GridRepCollection'
|
||||
import FilterBar from '~components/FilterBar'
|
||||
|
||||
const SavedRoute: React.FC = () => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Cookies
|
||||
// Set up cookies
|
||||
const [cookies] = useCookies(['account'])
|
||||
const headers = (cookies.account != null) ? {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
const headers = (cookies.account) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
|
||||
// Import translations
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Set up app-specific states
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [raidsLoading, setRaidsLoading] = useState(true)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
|
||||
// Filter states
|
||||
const [element, setElement] = useState<number | null>(null)
|
||||
const [raidId, setRaidId] = useState<string | null>(null)
|
||||
const [recencyInSeconds, setRecencyInSeconds] = useState<number | null>(null)
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
const [element, setElement] = useQueryState("element", {
|
||||
defaultValue: -1,
|
||||
parse: (query: string) => parseElement(query),
|
||||
serialize: value => serializeElement(value)
|
||||
})
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
|
||||
const [recency, setRecency] = useQueryState("recency", queryTypes.integer.withDefault(-1))
|
||||
|
||||
// Define transformers for element
|
||||
function parseElement(query: string) {
|
||||
let element: TeamElement | undefined =
|
||||
(query === 'all') ?
|
||||
allElement : elements.find(element => element.name.en.toLowerCase() === query)
|
||||
return (element) ? element.id : -1
|
||||
}
|
||||
|
||||
function serializeElement(value: number | undefined) {
|
||||
let name = ''
|
||||
|
||||
if (value != undefined) {
|
||||
if (value == -1)
|
||||
name = allElement.name.en.toLowerCase()
|
||||
else
|
||||
name = elements[value].name.en.toLowerCase()
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// Add scroll event listener for shadow on FilterBar on mount
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [])
|
||||
|
||||
// Handle errors
|
||||
const handleError = useCallback((error: any) => {
|
||||
if (error.response != null) {
|
||||
console.error(error)
|
||||
|
|
@ -47,51 +88,73 @@ const SavedRoute: React.FC = () => {
|
|||
}, [])
|
||||
|
||||
const fetchTeams = useCallback(() => {
|
||||
const filterParams = {
|
||||
const filters = {
|
||||
params: {
|
||||
element: element,
|
||||
raid: raidId,
|
||||
recency: recencyInSeconds
|
||||
},
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
element: (element != -1) ? element : undefined,
|
||||
raid: (raid) ? raid?.id : undefined,
|
||||
recency: (recency != -1) ? recency : undefined
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
api.savedTeams(filterParams)
|
||||
api.savedTeams({...filters, ...headers})
|
||||
.then(response => {
|
||||
const parties: Party[] = response.data
|
||||
setParties(parties.map((p: any) => p.party).sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
|
||||
setParties(parties.map((p: any) => p.party)
|
||||
.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(error => handleError(error))
|
||||
}, [element, raidId, recencyInSeconds, cookies.account, handleError])
|
||||
}, [element, raid, recency])
|
||||
|
||||
// Fetch all raids on mount, then find the raid in the URL if present
|
||||
useEffect(() => {
|
||||
fetchTeams()
|
||||
}, [fetchTeams])
|
||||
api.endpoints.raids.getAll()
|
||||
.then(response => {
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
|
||||
setRaids(cleanRaids)
|
||||
|
||||
function receiveFilters(element?: number, raid?: string, recency?: number) {
|
||||
if (element != null && element >= 0)
|
||||
setRaidsLoading(false)
|
||||
|
||||
const raid = cleanRaids.find(r => r.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
|
||||
return raid
|
||||
})
|
||||
}, [setRaids])
|
||||
|
||||
// When the element, raid or recency filter changes,
|
||||
// fetch all teams again.
|
||||
useEffect(() => {
|
||||
if (!raidsLoading) fetchTeams()
|
||||
}, [element, raid, recency])
|
||||
|
||||
// On first mount only, disable loading if we are fetching all teams
|
||||
useEffect(() => {
|
||||
if (raidSlug === 'all') {
|
||||
setRaidsLoading(false)
|
||||
fetchTeams()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Receive filters from the filter bar
|
||||
function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) {
|
||||
if (element == 0)
|
||||
setElement(0)
|
||||
else if (element)
|
||||
setElement(element)
|
||||
else
|
||||
setElement(null)
|
||||
|
||||
if (raids && raidSlug) {
|
||||
const raid = raids.find(raid => raid.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
setRaidSlug(raidSlug)
|
||||
}
|
||||
|
||||
if (raid && raid != '0')
|
||||
setRaidId(raid)
|
||||
else
|
||||
setRaidId(null)
|
||||
|
||||
if (recency && recency > 0)
|
||||
setRecencyInSeconds(recency)
|
||||
else
|
||||
setRecencyInSeconds(null)
|
||||
if (recency) setRecency(recency)
|
||||
}
|
||||
|
||||
// Methods: Favorites
|
||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
||||
if (favorited)
|
||||
unsaveFavorite(teamId)
|
||||
|
|
@ -133,6 +196,7 @@ const SavedRoute: React.FC = () => {
|
|||
})
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function handleScroll() {
|
||||
if (window.pageYOffset > 90)
|
||||
setScrolled(true)
|
||||
|
|
@ -158,8 +222,13 @@ const SavedRoute: React.FC = () => {
|
|||
<meta name="twitter:title" content="Your saved Teams" />
|
||||
</Head>
|
||||
|
||||
<FilterBar onFilter={receiveFilters} scrolled={scrolled}>
|
||||
<h1>{t('saved.title')}</h1>
|
||||
<FilterBar
|
||||
onFilter={receiveFilters}
|
||||
scrolled={scrolled}
|
||||
element={element}
|
||||
raidSlug={ (raidSlug) ? raidSlug : undefined }
|
||||
recency={recency}>
|
||||
<h1>{t('saved.title')}</h1>
|
||||
</FilterBar>
|
||||
|
||||
<section>
|
||||
|
|
@ -178,8 +247,7 @@ const SavedRoute: React.FC = () => {
|
|||
key={`party-${i}`}
|
||||
displayUser={true}
|
||||
onClick={goTo}
|
||||
onSave={toggleFavorite}
|
||||
/>
|
||||
onSave={toggleFavorite} />
|
||||
})
|
||||
}
|
||||
</GridRepCollection>
|
||||
|
|
|
|||
157
pages/teams.tsx
157
pages/teams.tsx
|
|
@ -1,46 +1,84 @@
|
|||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import Head from 'next/head'
|
||||
|
||||
import { useRouter } from 'next/router'
|
||||
import { useCookies } from 'react-cookie'
|
||||
import { useQueryState, queryTypes } from 'next-usequerystate'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
|
||||
|
||||
import api from '~utils/api'
|
||||
import { elements, allElement } from '~utils/Element'
|
||||
|
||||
import GridRep from '~components/GridRep'
|
||||
import GridRepCollection from '~components/GridRepCollection'
|
||||
import FilterBar from '~components/FilterBar'
|
||||
|
||||
const TeamsRoute: React.FC = () => {
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Cookies
|
||||
// Set up cookies
|
||||
const [cookies] = useCookies(['account'])
|
||||
const headers = (cookies.account != null) ? {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
const headers = (cookies.account) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
|
||||
// Import translations
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Set up app-specific states
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [raidsLoading, setRaidsLoading] = useState(true)
|
||||
const [scrolled, setScrolled] = useState(false)
|
||||
|
||||
// Set up page-specific states
|
||||
const [parties, setParties] = useState<Party[]>([])
|
||||
const [raids, setRaids] = useState<Raid[]>()
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
|
||||
// Filter states
|
||||
const [element, setElement] = useState<number | null>(null)
|
||||
const [raidId, setRaidId] = useState<string | null>(null)
|
||||
const [recencyInSeconds, setRecencyInSeconds] = useState<number | null>(null)
|
||||
// Set up filter-specific query states
|
||||
// Recency is in seconds
|
||||
const [element, setElement] = useQueryState("element", {
|
||||
defaultValue: -1,
|
||||
parse: (query: string) => parseElement(query),
|
||||
serialize: value => serializeElement(value)
|
||||
})
|
||||
const [raidSlug, setRaidSlug] = useQueryState("raid", { defaultValue: "all" })
|
||||
const [recency, setRecency] = useQueryState("recency", queryTypes.integer.withDefault(-1))
|
||||
|
||||
// Define transformers for element
|
||||
function parseElement(query: string) {
|
||||
let element: TeamElement | undefined =
|
||||
(query === 'all') ?
|
||||
allElement : elements.find(element => element.name.en.toLowerCase() === query)
|
||||
return (element) ? element.id : -1
|
||||
}
|
||||
|
||||
function serializeElement(value: number | undefined) {
|
||||
let name = ''
|
||||
|
||||
if (value != undefined) {
|
||||
if (value == -1)
|
||||
name = allElement.name.en.toLowerCase()
|
||||
else
|
||||
name = elements[value].name.en.toLowerCase()
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
// Add scroll event listener for shadow on FilterBar on mount
|
||||
useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll)
|
||||
return () => window.removeEventListener("scroll", handleScroll);
|
||||
}, [])
|
||||
|
||||
// Handle errors
|
||||
const handleError = useCallback((error: any) => {
|
||||
if (error.response != null) {
|
||||
console.error(error)
|
||||
|
|
@ -52,54 +90,71 @@ const TeamsRoute: React.FC = () => {
|
|||
const fetchTeams = useCallback(() => {
|
||||
const filters = {
|
||||
params: {
|
||||
element: element,
|
||||
raid: raidId,
|
||||
recency: recencyInSeconds
|
||||
element: (element != -1) ? element : undefined,
|
||||
raid: (raid) ? raid.id : undefined,
|
||||
recency: (recency != -1) ? recency : undefined
|
||||
}
|
||||
}
|
||||
|
||||
const headers = (cookies.account) ? {
|
||||
headers: {
|
||||
'Authorization': `Bearer ${cookies.account.access_token}`
|
||||
}
|
||||
} : {}
|
||||
|
||||
const params = {...filters, ...headers}
|
||||
|
||||
setLoading(true)
|
||||
|
||||
api.endpoints.parties.getAll(params)
|
||||
api.endpoints.parties.getAll({...filters, ...headers})
|
||||
.then(response => {
|
||||
const parties: Party[] = response.data
|
||||
setParties(parties.map((p: any) => p.party).sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
|
||||
setParties(parties.map((p: any) => p.party)
|
||||
.sort((a, b) => (a.created_at > b.created_at) ? -1 : 1))
|
||||
})
|
||||
.then(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
.catch(error => handleError(error))
|
||||
}, [element, raidId, recencyInSeconds, cookies.account, handleError])
|
||||
}, [element, raid, recency])
|
||||
|
||||
// Fetch all raids on mount, then find the raid in the URL if present
|
||||
useEffect(() => {
|
||||
fetchTeams()
|
||||
}, [fetchTeams])
|
||||
api.endpoints.raids.getAll()
|
||||
.then(response => {
|
||||
const cleanRaids: Raid[] = response.data.map((r: any) => r.raid)
|
||||
setRaids(cleanRaids)
|
||||
|
||||
function receiveFilters(element?: number, raid?: string, recency?: number) {
|
||||
if (element != null && element >= 0)
|
||||
setRaidsLoading(false)
|
||||
|
||||
const raid = cleanRaids.find(r => r.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
|
||||
return raid
|
||||
})
|
||||
}, [setRaids])
|
||||
|
||||
// When the element, raid or recency filter changes,
|
||||
// fetch all teams again.
|
||||
useEffect(() => {
|
||||
if (!raidsLoading) fetchTeams()
|
||||
}, [element, raid, recency])
|
||||
|
||||
// On first mount only, disable loading if we are fetching all teams
|
||||
useEffect(() => {
|
||||
if (raidSlug === 'all') {
|
||||
setRaidsLoading(false)
|
||||
fetchTeams()
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Receive filters from the filter bar
|
||||
function receiveFilters({ element, raidSlug, recency }: {element?: number, raidSlug?: string, recency?: number}) {
|
||||
if (element == 0)
|
||||
setElement(0)
|
||||
else if (element)
|
||||
setElement(element)
|
||||
else
|
||||
setElement(null)
|
||||
|
||||
if (raids && raidSlug) {
|
||||
const raid = raids.find(raid => raid.slug === raidSlug)
|
||||
setRaid(raid)
|
||||
setRaidSlug(raidSlug)
|
||||
}
|
||||
|
||||
if (raid && raid != '0')
|
||||
setRaidId(raid)
|
||||
else
|
||||
setRaidId(null)
|
||||
|
||||
if (recency && recency > 0)
|
||||
setRecencyInSeconds(recency)
|
||||
else
|
||||
setRecencyInSeconds(null)
|
||||
if (recency) setRecency(recency)
|
||||
}
|
||||
|
||||
// Methods: Favorites
|
||||
function toggleFavorite(teamId: string, favorited: boolean) {
|
||||
if (favorited)
|
||||
unsaveFavorite(teamId)
|
||||
|
|
@ -141,6 +196,7 @@ const TeamsRoute: React.FC = () => {
|
|||
})
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function handleScroll() {
|
||||
if (window.pageYOffset > 90)
|
||||
setScrolled(true)
|
||||
|
|
@ -167,8 +223,14 @@ const TeamsRoute: React.FC = () => {
|
|||
<meta name="twitter:title" content="Discover Teams" />
|
||||
<meta name="twitter:description" content="Find different Granblue Fantasy teams by raid, element or recency" />
|
||||
</Head>
|
||||
<FilterBar onFilter={receiveFilters} scrolled={scrolled}>
|
||||
<h1>{t('teams.title')}</h1>
|
||||
|
||||
<FilterBar
|
||||
onFilter={receiveFilters}
|
||||
scrolled={scrolled}
|
||||
element={element}
|
||||
raidSlug={ (raidSlug) ? raidSlug : undefined }
|
||||
recency={recency}>
|
||||
<h1>{t('teams.title')}</h1>
|
||||
</FilterBar>
|
||||
|
||||
<section>
|
||||
|
|
@ -187,8 +249,7 @@ const TeamsRoute: React.FC = () => {
|
|||
key={`party-${i}`}
|
||||
displayUser={true}
|
||||
onClick={goTo}
|
||||
onSave={toggleFavorite}
|
||||
/>
|
||||
onSave={toggleFavorite} />
|
||||
})
|
||||
}
|
||||
</GridRepCollection>
|
||||
|
|
|
|||
3
types/Raid.d.ts
vendored
3
types/Raid.d.ts
vendored
|
|
@ -5,7 +5,8 @@ interface Raid {
|
|||
en: string
|
||||
ja: string
|
||||
}
|
||||
slug: string
|
||||
level: number
|
||||
group: number
|
||||
element: TeamElement
|
||||
element: number
|
||||
}
|
||||
7
types/TeamElement.d.ts
vendored
Normal file
7
types/TeamElement.d.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
interface TeamElement {
|
||||
id: number,
|
||||
name: {
|
||||
en: string,
|
||||
ja: string
|
||||
}
|
||||
}
|
||||
59
utils/Element.tsx
Normal file
59
utils/Element.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
export const allElement: TeamElement = {
|
||||
id: -1,
|
||||
name: {
|
||||
en: "All",
|
||||
ja: "全s"
|
||||
}
|
||||
}
|
||||
|
||||
export const elements: TeamElement[] = [
|
||||
{
|
||||
id: 0,
|
||||
name: {
|
||||
en: "Null",
|
||||
ja: "無"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: {
|
||||
en: "Wind",
|
||||
ja: "風"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: {
|
||||
en: "Fire",
|
||||
ja: "火"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: {
|
||||
en: "Water",
|
||||
ja: "水"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: {
|
||||
en: "Earth",
|
||||
ja: "土"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: {
|
||||
en: "Dark",
|
||||
ja: "闇"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: {
|
||||
en: "Light",
|
||||
ja: "光"
|
||||
}
|
||||
}
|
||||
]
|
||||
124
utils/raidGroups.tsx
Normal file
124
utils/raidGroups.tsx
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
interface RaidGroup {
|
||||
name: {
|
||||
[key: string]: string
|
||||
en: string
|
||||
ja: string
|
||||
}
|
||||
}
|
||||
|
||||
export const raidGroups: RaidGroup[] = [
|
||||
{
|
||||
name: {
|
||||
en: 'Assorted',
|
||||
ja: 'その他'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Guild Wars',
|
||||
ja: '星の古戦場'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Omega',
|
||||
ja: 'マグナ'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'T1 Summons',
|
||||
ja: '召喚石マルチ1(旧)'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'T2 Summons',
|
||||
ja: '召喚石マルチ2(新)'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Primarchs',
|
||||
ja: '四大天使'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Nightmare',
|
||||
ja: 'HELL'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Omega (Impossible)',
|
||||
ja: 'マグナHL'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Omega II',
|
||||
ja: 'マグナII'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Tier 1 Summons (Impossible)',
|
||||
ja: '旧召喚石HL'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Tier 3 Summons',
|
||||
ja: 'エピックHL'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Ennead',
|
||||
ja: 'エニアド'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Malice',
|
||||
ja: 'マリス'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: '6-Star Raids',
|
||||
ja: '★★★★★★'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Six-Dragons',
|
||||
ja: '六竜HL'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Nightmare (Impossible)',
|
||||
ja: '高級HELL'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Arcarum: Replicard Sandbox',
|
||||
ja: 'アーカルム レプリカルド・サンドボックス'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Astrals',
|
||||
ja: '星の民'
|
||||
}
|
||||
},
|
||||
{
|
||||
name: {
|
||||
en: 'Super Ultimate',
|
||||
ja: 'スーパーアルティメット'
|
||||
}
|
||||
}
|
||||
]
|
||||
Loading…
Reference in a new issue