commit
3f24886b98
42 changed files with 677 additions and 361 deletions
|
|
@ -80,6 +80,10 @@ const AccountModal = (props: Props) => {
|
|||
// const [privateProfile, setPrivateProfile] = useState(false)
|
||||
|
||||
// Setup
|
||||
const [pictureOpen, setPictureOpen] = useState(false)
|
||||
const [genderOpen, setGenderOpen] = useState(false)
|
||||
const [languageOpen, setLanguageOpen] = useState(false)
|
||||
const [themeOpen, setThemeOpen] = useState(false)
|
||||
|
||||
// UI management
|
||||
function openChange(open: boolean) {
|
||||
|
|
@ -87,16 +91,10 @@ const AccountModal = (props: Props) => {
|
|||
}
|
||||
|
||||
function openSelect(name: 'picture' | 'gender' | 'language' | 'theme') {
|
||||
const stateVars = selectOpenState
|
||||
Object.keys(stateVars).forEach((key) => {
|
||||
if (key === name) {
|
||||
stateVars[name] = true
|
||||
} else {
|
||||
stateVars[key] = false
|
||||
}
|
||||
})
|
||||
|
||||
setSelectOpenState(stateVars)
|
||||
setPictureOpen(name === 'picture' ? !pictureOpen : false)
|
||||
setGenderOpen(name === 'gender' ? !genderOpen : false)
|
||||
setLanguageOpen(name === 'language' ? !languageOpen : false)
|
||||
setThemeOpen(name === 'theme' ? !themeOpen : false)
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
|
@ -117,6 +115,14 @@ const AccountModal = (props: Props) => {
|
|||
setAppTheme(value)
|
||||
}
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
if (pictureOpen || genderOpen || languageOpen || themeOpen) {
|
||||
return event.preventDefault()
|
||||
} else {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
// API calls
|
||||
function update(event: React.FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault()
|
||||
|
|
@ -189,9 +195,10 @@ const AccountModal = (props: Props) => {
|
|||
description={t('modals.settings.descriptions.picture')}
|
||||
className="Image"
|
||||
label={t('modals.settings.labels.picture')}
|
||||
open={selectOpenState.picture}
|
||||
onClick={() => openSelect('picture')}
|
||||
open={pictureOpen}
|
||||
onOpenChange={() => openSelect('picture')}
|
||||
onChange={handlePictureChange}
|
||||
onClose={() => setPictureOpen(false)}
|
||||
imageAlt={t('modals.settings.labels.image_alt')}
|
||||
imageClass={pictureData.find((i) => i.filename === picture)?.element}
|
||||
imageSrc={[`/profile/${picture}.png`, `/profile/${picture}@2x.png 2x`]}
|
||||
|
|
@ -206,9 +213,10 @@ const AccountModal = (props: Props) => {
|
|||
name="gender"
|
||||
description={t('modals.settings.descriptions.gender')}
|
||||
label={t('modals.settings.labels.gender')}
|
||||
open={selectOpenState.gender}
|
||||
onClick={() => openSelect('gender')}
|
||||
open={genderOpen}
|
||||
onOpenChange={() => openSelect('gender')}
|
||||
onChange={handleGenderChange}
|
||||
onClose={() => setGenderOpen(false)}
|
||||
value={`${gender}`}
|
||||
>
|
||||
<SelectItem key="gran" value="0">
|
||||
|
|
@ -224,9 +232,10 @@ const AccountModal = (props: Props) => {
|
|||
<SelectTableField
|
||||
name="language"
|
||||
label={t('modals.settings.labels.language')}
|
||||
open={selectOpenState.language}
|
||||
onClick={() => openSelect('language')}
|
||||
open={languageOpen}
|
||||
onOpenChange={() => openSelect('language')}
|
||||
onChange={handleLanguageChange}
|
||||
onClose={() => setLanguageOpen(false)}
|
||||
value={language}
|
||||
>
|
||||
<SelectItem key="en" value="en">
|
||||
|
|
@ -242,9 +251,10 @@ const AccountModal = (props: Props) => {
|
|||
<SelectTableField
|
||||
name="theme"
|
||||
label={t('modals.settings.labels.theme')}
|
||||
open={selectOpenState.theme}
|
||||
onClick={() => openSelect('theme')}
|
||||
open={themeOpen}
|
||||
onOpenChange={() => openSelect('theme')}
|
||||
onChange={handleThemeChange}
|
||||
onClose={() => setThemeOpen(false)}
|
||||
value={theme}
|
||||
>
|
||||
<SelectItem key="system" value="system">
|
||||
|
|
@ -274,7 +284,11 @@ const AccountModal = (props: Props) => {
|
|||
<span>{t('menu.settings')}</span>
|
||||
</li>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="Account Dialog">
|
||||
<DialogContent
|
||||
className="Account Dialog"
|
||||
onOpenAutoFocus={(event: Event) => {}}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTop">
|
||||
<DialogTitle className="SubTitle">
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface Props {
|
|||
object: 'character' | 'weapon'
|
||||
awakeningType?: number
|
||||
awakeningLevel?: number
|
||||
onOpenChange: (open: boolean) => void
|
||||
sendValidity: (isValid: boolean) => void
|
||||
sendValues: (type: number, level: number) => void
|
||||
}
|
||||
|
|
@ -81,6 +82,11 @@ const AwakeningSelect = (props: Props) => {
|
|||
// Classes
|
||||
function changeOpen() {
|
||||
setOpen(!open)
|
||||
props.onOpenChange(!open)
|
||||
}
|
||||
|
||||
function onClose() {
|
||||
props.onOpenChange(false)
|
||||
}
|
||||
|
||||
function generateOptions(object: 'character' | 'weapon') {
|
||||
|
|
@ -165,7 +171,8 @@ const AwakeningSelect = (props: Props) => {
|
|||
value={`${awakeningType}`}
|
||||
open={open}
|
||||
onValueChange={handleSelectChange}
|
||||
onClick={() => changeOpen()}
|
||||
onOpenChange={() => changeOpen()}
|
||||
onClose={onClose}
|
||||
triggerClass="modal"
|
||||
>
|
||||
{generateOptions(props.object)}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ interface ErrorMap {
|
|||
interface Props {
|
||||
axType: number
|
||||
currentSkills?: SimpleAxSkill[]
|
||||
onOpenChange: (index: 1 | 2, open: boolean) => void
|
||||
sendValidity: (isValid: boolean) => void
|
||||
sendValues: (
|
||||
primaryAxModifier: number,
|
||||
|
|
@ -193,9 +194,22 @@ const AXSelect = (props: Props) => {
|
|||
: undefined
|
||||
}
|
||||
|
||||
function openSelect(ref: ForwardedRef<HTMLButtonElement>) {
|
||||
if (ref === primaryAxModifierSelect) setOpenAX1(!openAX1)
|
||||
if (ref === secondaryAxModifierSelect) setOpenAX2(!openAX2)
|
||||
function openSelect(index: 1 | 2) {
|
||||
if (index === 1) {
|
||||
setOpenAX1(!openAX1)
|
||||
setOpenAX2(false)
|
||||
props.onOpenChange(1, !openAX1)
|
||||
props.onOpenChange(2, false)
|
||||
} else if (index === 2) {
|
||||
setOpenAX2(!openAX2)
|
||||
setOpenAX1(false)
|
||||
props.onOpenChange(2, !openAX2)
|
||||
props.onOpenChange(1, false)
|
||||
}
|
||||
}
|
||||
|
||||
function onClose(index: 1 | 2) {
|
||||
props.onOpenChange(index, false)
|
||||
}
|
||||
|
||||
function generateOptions(modifierSet: number) {
|
||||
|
|
@ -392,8 +406,9 @@ const AXSelect = (props: Props) => {
|
|||
key="ax1"
|
||||
value={`${primaryAxModifier}`}
|
||||
open={openAX1}
|
||||
onClose={() => onClose(1)}
|
||||
onOpenChange={() => openSelect(1)}
|
||||
onValueChange={handleAX1SelectChange}
|
||||
onClick={() => openSelect(primaryAxModifierSelect)}
|
||||
triggerClass="modal"
|
||||
>
|
||||
{generateOptions(0)}
|
||||
|
|
@ -419,8 +434,9 @@ const AXSelect = (props: Props) => {
|
|||
key="ax2"
|
||||
value={`${secondaryAxModifier}`}
|
||||
open={openAX2}
|
||||
onClose={() => onClose(2)}
|
||||
onOpenChange={() => openSelect(2)}
|
||||
onValueChange={handleAX2SelectChange}
|
||||
onClick={() => openSelect(secondaryAxModifierSelect)}
|
||||
triggerClass="modal"
|
||||
ref={secondaryAxModifierSelect}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -17,6 +17,10 @@
|
|||
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.Input {
|
||||
padding: $unit * 1.5 $unit-2x;
|
||||
}
|
||||
|
||||
.Counter {
|
||||
color: $grey-55;
|
||||
font-weight: $bold;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ interface Props
|
|||
extends React.DetailedHTMLProps<
|
||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {}
|
||||
> {
|
||||
onEscapeKeyDown: (event: KeyboardEvent) => void
|
||||
onOpenAutoFocus: (event: Event) => void
|
||||
}
|
||||
|
||||
export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
||||
function dialog({ children, ...props }, forwardedRef) {
|
||||
|
|
@ -25,6 +28,8 @@ export const DialogContent = React.forwardRef<HTMLDivElement, Props>(
|
|||
<DialogPrimitive.Content
|
||||
className={classes}
|
||||
{...props}
|
||||
onOpenAutoFocus={props.onOpenAutoFocus}
|
||||
onEscapeKeyDown={props.onEscapeKeyDown}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
{children}
|
||||
|
|
|
|||
|
|
@ -60,12 +60,18 @@ const FilterBar = (props: Props) => {
|
|||
props.onFilter({ raidSlug: slug })
|
||||
}
|
||||
|
||||
function onSelectChange(name: 'element' | 'recency') {
|
||||
setElementOpen(name === 'element' ? !elementOpen : false)
|
||||
setRecencyOpen(name === 'recency' ? !recencyOpen : false)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
{props.children}
|
||||
<Select
|
||||
value={`${props.element}`}
|
||||
open={elementOpen}
|
||||
onOpenChange={() => onSelectChange('element')}
|
||||
onValueChange={elementSelectChanged}
|
||||
onClick={openElementSelect}
|
||||
>
|
||||
|
|
@ -106,6 +112,7 @@ const FilterBar = (props: Props) => {
|
|||
value={`${props.recency}`}
|
||||
trigger={'All time'}
|
||||
open={recencyOpen}
|
||||
onOpenChange={() => onSelectChange('recency')}
|
||||
onValueChange={recencySelectChanged}
|
||||
onClick={openRecencySelect}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -99,6 +99,10 @@
|
|||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
a.user:hover {
|
||||
color: var(--link-text-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.raid,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
|
@ -132,11 +133,28 @@ const GridRep = (props: Props) => {
|
|||
} else return <div className="no-user" />
|
||||
}
|
||||
|
||||
const linkedAttribution = () => (
|
||||
<Link href={`/${props.user ? props.user.username : '#'}`}>
|
||||
<a
|
||||
className={userClass}
|
||||
href={`/${props.user ? props.user.username : '#'}`}
|
||||
>
|
||||
{userImage()}
|
||||
{props.user ? props.user.username : t('no_user')}
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
|
||||
const unlinkedAttribution = () => (
|
||||
<div className={userClass}>
|
||||
{userImage()}
|
||||
{props.user ? props.user.username : t('no_user')}
|
||||
</div>
|
||||
)
|
||||
|
||||
const details = (
|
||||
<div className="Details">
|
||||
<h2 className={titleClass} onClick={navigate}>
|
||||
{props.name ? props.name : t('no_title')}
|
||||
</h2>
|
||||
<h2 className={titleClass}>{props.name ? props.name : t('no_title')}</h2>
|
||||
<div className="bottom">
|
||||
<div className={raidClass}>
|
||||
{props.raid ? props.raid.name[locale] : t('no_raid')}
|
||||
|
|
@ -152,7 +170,7 @@ const GridRep = (props: Props) => {
|
|||
<div className="Details">
|
||||
<div className="top">
|
||||
<div className="info">
|
||||
<h2 className={titleClass} onClick={navigate}>
|
||||
<h2 className={titleClass}>
|
||||
{props.name ? props.name : t('no_title')}
|
||||
</h2>
|
||||
<div className={raidClass}>
|
||||
|
|
@ -162,23 +180,25 @@ const GridRep = (props: Props) => {
|
|||
{account.authorized &&
|
||||
((props.user && account.user && account.user.id !== props.user.id) ||
|
||||
!props.user) ? (
|
||||
<Button
|
||||
className="Save"
|
||||
accessoryIcon={<SaveIcon className="stroke" />}
|
||||
active={props.favorited}
|
||||
contained={true}
|
||||
buttonSize="small"
|
||||
onClick={sendSaveData}
|
||||
/>
|
||||
<Link href="#">
|
||||
<a href="#">
|
||||
<Button
|
||||
className="Save"
|
||||
accessoryIcon={<SaveIcon className="stroke" />}
|
||||
active={props.favorited}
|
||||
contained={true}
|
||||
buttonSize="small"
|
||||
onClick={sendSaveData}
|
||||
/>
|
||||
</a>
|
||||
</Link>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
<div className="bottom">
|
||||
<div className={userClass}>
|
||||
{userImage()}
|
||||
{props.user ? props.user.username : t('no_user')}
|
||||
</div>
|
||||
{props.user ? linkedAttribution() : unlinkedAttribution()}
|
||||
|
||||
<time className="last-updated" dateTime={props.createdAt.toISOString()}>
|
||||
{formatTimeAgo(props.createdAt, locale)}
|
||||
</time>
|
||||
|
|
@ -187,25 +207,27 @@ const GridRep = (props: Props) => {
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="GridRep">
|
||||
{props.displayUser ? detailsWithUsername : details}
|
||||
<div className="Grid" onClick={navigate}>
|
||||
<div className="weapon grid_mainhand">{generateMainhandImage()}</div>
|
||||
<Link href={`/p/${props.shortcode}`}>
|
||||
<a className="GridRep">
|
||||
{props.displayUser ? detailsWithUsername : details}
|
||||
<div className="Grid">
|
||||
<div className="weapon grid_mainhand">{generateMainhandImage()}</div>
|
||||
|
||||
<ul className="grid_weapons">
|
||||
{Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
<li
|
||||
key={`${props.shortcode}-${i}`}
|
||||
className="weapon grid_weapon"
|
||||
>
|
||||
{generateGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="grid_weapons">
|
||||
{Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
<li
|
||||
key={`${props.shortcode}-${i}`}
|
||||
className="weapon grid_weapon"
|
||||
>
|
||||
{generateGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.GridRepCollection {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto auto;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
width: fit-content;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
.Header {
|
||||
display: flex;
|
||||
margin-bottom: $unit-2x;
|
||||
margin-bottom: $unit;
|
||||
width: 100%;
|
||||
|
||||
&.bottom {
|
||||
|
|
@ -16,9 +16,10 @@
|
|||
.dropdown {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding-bottom: $unit;
|
||||
|
||||
&:hover {
|
||||
padding-right: 50px;
|
||||
padding-right: $unit-4x;
|
||||
|
||||
.Button {
|
||||
background: var(--button-bg-hover);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import Select from '~components/Select'
|
||||
import SelectItem from '~components/SelectItem'
|
||||
|
|
@ -26,6 +27,9 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
|||
const router = useRouter()
|
||||
const locale = router.locale || 'en'
|
||||
|
||||
// Set up translation
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Create snapshot of app state
|
||||
const { party } = useSnapshot(appState)
|
||||
|
||||
|
|
@ -106,11 +110,12 @@ const JobDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
|||
placeholder={'Select a class...'}
|
||||
open={open}
|
||||
onClick={openJobSelect}
|
||||
onOpenChange={() => setOpen(!open)}
|
||||
onValueChange={handleChange}
|
||||
triggerClass="Job"
|
||||
>
|
||||
<SelectItem key={-1} value="no-job">
|
||||
No class
|
||||
{t('no_job')}
|
||||
</SelectItem>
|
||||
{sortedJobs
|
||||
? Object.keys(sortedJobs)
|
||||
|
|
|
|||
|
|
@ -3,29 +3,4 @@
|
|||
flex-direction: column;
|
||||
gap: calc($unit / 2);
|
||||
margin-bottom: $unit;
|
||||
|
||||
.Button {
|
||||
font-size: $font-regular;
|
||||
padding: ($unit * 1.5) ($unit * 2);
|
||||
width: 100%;
|
||||
|
||||
&.btn-disabled {
|
||||
background: $grey-90;
|
||||
color: $grey-70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(.btn-disabled) {
|
||||
background: $grey-90;
|
||||
color: $grey-50;
|
||||
|
||||
&:hover {
|
||||
background: $grey-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
background: $grey-90;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,6 +171,15 @@ const LoginModal = (props: Props) => {
|
|||
})
|
||||
}
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function onOpenAutoFocus(event: Event) {
|
||||
event.preventDefault()
|
||||
if (emailInput.current) emailInput.current.focus()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogTrigger asChild>
|
||||
|
|
@ -178,7 +187,11 @@ const LoginModal = (props: Props) => {
|
|||
<span>{t('menu.login')}</span>
|
||||
</li>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="Login Dialog">
|
||||
<DialogContent
|
||||
className="Login Dialog"
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTitle">
|
||||
<h1>{t('modals.login.title')}</h1>
|
||||
|
|
@ -206,7 +219,10 @@ const LoginModal = (props: Props) => {
|
|||
ref={passwordInput}
|
||||
/>
|
||||
|
||||
<Button text={t('modals.login.buttons.confirm')} />
|
||||
<Button
|
||||
disabled={!formValid}
|
||||
text={t('modals.login.buttons.confirm')}
|
||||
/>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
|
|
|||
|
|
@ -238,6 +238,8 @@ const Party = (props: Props) => {
|
|||
{navigation}
|
||||
<section id="Party">{currentGrid()}</section>
|
||||
<PartyDetails
|
||||
party={props.team}
|
||||
new={props.new || false}
|
||||
editable={party.editable}
|
||||
updateCallback={updateDetails}
|
||||
deleteCallback={deleteTeam}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
|
@ -13,18 +14,20 @@ import CharLimitedFieldset from '~components/CharLimitedFieldset'
|
|||
import RaidDropdown from '~components/RaidDropdown'
|
||||
import TextFieldset from '~components/TextFieldset'
|
||||
|
||||
import { accountState } from '~utils/accountState'
|
||||
import { appState } from '~utils/appState'
|
||||
import { formatTimeAgo } from '~utils/timeAgo'
|
||||
|
||||
import CheckIcon from '~public/icons/Check.svg'
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import EditIcon from '~public/icons/Edit.svg'
|
||||
|
||||
import './index.scss'
|
||||
import Link from 'next/link'
|
||||
import { formatTimeAgo } from '~utils/timeAgo'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
party?: Party
|
||||
new: boolean
|
||||
editable: boolean
|
||||
updateCallback: (name?: string, description?: string, raid?: Raid) => void
|
||||
deleteCallback: (
|
||||
|
|
@ -42,23 +45,24 @@ const PartyDetails = (props: Props) => {
|
|||
const nameInput = React.createRef<HTMLInputElement>()
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [raidSlug, setRaidSlug] = useState('')
|
||||
|
||||
const readOnlyClasses = classNames({
|
||||
PartyDetails: true,
|
||||
ReadOnly: true,
|
||||
Visible: !party.detailsVisible,
|
||||
Visible: true,
|
||||
})
|
||||
|
||||
const editableClasses = classNames({
|
||||
PartyDetails: true,
|
||||
Editable: true,
|
||||
Visible: party.detailsVisible,
|
||||
Visible: open,
|
||||
})
|
||||
|
||||
const emptyClasses = classNames({
|
||||
EmptyDetails: true,
|
||||
Visible: !party.detailsVisible,
|
||||
Visible: true,
|
||||
})
|
||||
|
||||
const userClass = classNames({
|
||||
|
|
@ -99,7 +103,7 @@ const PartyDetails = (props: Props) => {
|
|||
}
|
||||
|
||||
function toggleDetails() {
|
||||
appState.party.detailsVisible = !appState.party.detailsVisible
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
function receiveRaid(slug?: string) {
|
||||
|
|
@ -115,34 +119,57 @@ const PartyDetails = (props: Props) => {
|
|||
toggleDetails()
|
||||
}
|
||||
|
||||
const userImage = () => {
|
||||
if (party.user)
|
||||
const userImage = (picture?: string, element?: string) => {
|
||||
if (picture && element)
|
||||
return (
|
||||
<img
|
||||
alt={party.user.avatar.picture}
|
||||
className={`profile ${party.user.avatar.element}`}
|
||||
srcSet={`/profile/${party.user.avatar.picture}.png,
|
||||
/profile/${party.user.avatar.picture}@2x.png 2x`}
|
||||
src={`/profile/${party.user.avatar.picture}.png`}
|
||||
alt={picture}
|
||||
className={`profile ${element}`}
|
||||
srcSet={`/profile/${picture}.png,
|
||||
/profile/${picture}@2x.png 2x`}
|
||||
src={`/profile/${picture}.png`}
|
||||
/>
|
||||
)
|
||||
else return <div className="no-user" />
|
||||
}
|
||||
|
||||
const userBlock = () => {
|
||||
const userBlock = (username?: string, picture?: string, element?: string) => {
|
||||
return (
|
||||
<div className={userClass}>
|
||||
{userImage()}
|
||||
{party.user ? party.user.username : t('no_user')}
|
||||
{userImage(picture, element)}
|
||||
{username ? username : t('no_user')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const linkedUserBlock = (user: User) => {
|
||||
const renderUserBlock = () => {
|
||||
let username, picture, element
|
||||
if (accountState.account.authorized && props.new) {
|
||||
username = accountState.account.user?.username
|
||||
picture = accountState.account.user?.picture
|
||||
element = accountState.account.user?.element
|
||||
} else if (party.user && !props.new) {
|
||||
username = party.user.username
|
||||
picture = party.user.avatar.picture
|
||||
element = party.user.avatar.element
|
||||
}
|
||||
|
||||
if (username && picture && element) {
|
||||
return linkedUserBlock(username, picture, element)
|
||||
} else if (!props.new) {
|
||||
return userBlock()
|
||||
}
|
||||
}
|
||||
|
||||
const linkedUserBlock = (
|
||||
username?: string,
|
||||
picture?: string,
|
||||
element?: string
|
||||
) => {
|
||||
return (
|
||||
<div>
|
||||
<Link href={`/${user.username}`} passHref>
|
||||
<a className={linkClass}>{userBlock()}</a>
|
||||
<Link href={`/${username}`} passHref>
|
||||
<a className={linkClass}>{userBlock(username, picture, element)}</a>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -202,7 +229,7 @@ const PartyDetails = (props: Props) => {
|
|||
<CharLimitedFieldset
|
||||
fieldName="name"
|
||||
placeholder="Name your team"
|
||||
value={party.name}
|
||||
value={props.party?.name}
|
||||
limit={50}
|
||||
onChange={handleInputChange}
|
||||
error={errors.name}
|
||||
|
|
@ -210,7 +237,7 @@ const PartyDetails = (props: Props) => {
|
|||
/>
|
||||
<RaidDropdown
|
||||
showAllRaidsOption={false}
|
||||
currentRaid={party.raid ? party.raid.slug : undefined}
|
||||
currentRaid={props.party?.raid ? props.party?.raid.slug : undefined}
|
||||
onChange={receiveRaid}
|
||||
/>
|
||||
<TextFieldset
|
||||
|
|
@ -218,7 +245,7 @@ const PartyDetails = (props: Props) => {
|
|||
placeholder={
|
||||
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 1 first\nGood luck with RNG!'
|
||||
}
|
||||
value={party.description}
|
||||
value={props.party?.description}
|
||||
onChange={handleTextAreaChange}
|
||||
error={errors.description}
|
||||
ref={descriptionInput}
|
||||
|
|
@ -248,9 +275,9 @@ const PartyDetails = (props: Props) => {
|
|||
{party.name ? party.name : 'Untitled'}
|
||||
</h1>
|
||||
<div className="attribution">
|
||||
{party.user ? linkedUserBlock(party.user) : userBlock()}
|
||||
{renderUserBlock()}
|
||||
{party.raid ? linkedRaidBlock(party.raid) : ''}
|
||||
{party.created_at != undefined ? (
|
||||
{party.created_at != '' ? (
|
||||
<time
|
||||
className="last-updated"
|
||||
dateTime={new Date(party.created_at).toString()}
|
||||
|
|
@ -284,25 +311,9 @@ const PartyDetails = (props: Props) => {
|
|||
</section>
|
||||
)
|
||||
|
||||
const emptyDetails = (
|
||||
<div className={emptyClasses}>
|
||||
{party.editable ? (
|
||||
<Button
|
||||
accessoryIcon={<EditIcon />}
|
||||
text={t('buttons.show_info')}
|
||||
onClick={toggleDetails}
|
||||
/>
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{editable && (party.name || party.description || party.raid)
|
||||
? readOnly
|
||||
: emptyDetails}
|
||||
{readOnly}
|
||||
{editable}
|
||||
</React.Fragment>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ const RaidDropdown = React.forwardRef<HTMLSelectElement, Props>(
|
|||
value={props.currentRaid}
|
||||
placeholder={'Select a raid...'}
|
||||
open={open}
|
||||
onOpenChange={() => setOpen(!open)}
|
||||
onClick={openRaidSelect}
|
||||
onValueChange={handleChange}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -340,10 +340,24 @@ const SearchModal = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
event.preventDefault()
|
||||
openChange()
|
||||
}
|
||||
|
||||
function onOpenAutoFocus(event: Event) {
|
||||
event.preventDefault()
|
||||
if (searchInput.current) searchInput.current.focus()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||
<DialogContent className="Search Dialog">
|
||||
<DialogContent
|
||||
className="Search Dialog"
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<div id="Header">
|
||||
<div id="Bar">
|
||||
<Input
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ interface Props
|
|||
open: boolean
|
||||
trigger?: React.ReactNode
|
||||
children?: React.ReactNode
|
||||
onClick?: () => void
|
||||
onOpenChange?: () => void
|
||||
onValueChange?: (value: string) => void
|
||||
onClose?: () => void
|
||||
triggerClass?: string
|
||||
}
|
||||
|
||||
|
|
@ -24,8 +25,13 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
|||
props: Props,
|
||||
forwardedRef
|
||||
) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const [value, setValue] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(props.open)
|
||||
}, [props.open])
|
||||
|
||||
useEffect(() => {
|
||||
if (props.value && props.value !== '') setValue(`${props.value}`)
|
||||
else setValue('')
|
||||
|
|
@ -36,10 +42,27 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
|||
if (props.onValueChange) props.onValueChange(newValue)
|
||||
}
|
||||
|
||||
function onCloseAutoFocus() {
|
||||
setOpen(false)
|
||||
if (props.onClose) props.onClose()
|
||||
}
|
||||
|
||||
function onEscapeKeyDown() {
|
||||
setOpen(false)
|
||||
if (props.onClose) props.onClose()
|
||||
}
|
||||
|
||||
function onPointerDownOutside() {
|
||||
setOpen(false)
|
||||
if (props.onClose) props.onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<RadixSelect.Root
|
||||
open={open}
|
||||
value={value !== '' ? value : undefined}
|
||||
onValueChange={onValueChange}
|
||||
onOpenChange={props.onOpenChange}
|
||||
>
|
||||
<RadixSelect.Trigger
|
||||
className={classNames('SelectTrigger', props.triggerClass)}
|
||||
|
|
@ -53,7 +76,11 @@ const Select = React.forwardRef<HTMLButtonElement, Props>(function Select(
|
|||
</RadixSelect.Trigger>
|
||||
|
||||
<RadixSelect.Portal className="Select">
|
||||
<RadixSelect.Content>
|
||||
<RadixSelect.Content
|
||||
onCloseAutoFocus={onCloseAutoFocus}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onPointerDownOutside={onPointerDownOutside}
|
||||
>
|
||||
<RadixSelect.ScrollUpButton className="Scroll Up">
|
||||
<ArrowIcon />
|
||||
</RadixSelect.ScrollUpButton>
|
||||
|
|
|
|||
|
|
@ -15,8 +15,9 @@ interface Props {
|
|||
imageClass?: string
|
||||
imageSrc?: string[]
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
onOpenChange: () => void
|
||||
onChange: (value: string) => void
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const SelectTableField = (props: Props) => {
|
||||
|
|
@ -53,8 +54,9 @@ const SelectTableField = (props: Props) => {
|
|||
<Select
|
||||
name={props.name}
|
||||
open={props.open}
|
||||
onClick={props.onClick}
|
||||
onOpenChange={props.onOpenChange}
|
||||
onValueChange={props.onChange}
|
||||
onClose={props.onClose}
|
||||
triggerClass={classNames({ Bound: true, Table: true })}
|
||||
value={value}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,27 +4,6 @@
|
|||
gap: calc($unit / 2);
|
||||
margin-bottom: $unit;
|
||||
|
||||
.Button {
|
||||
font-size: $font-regular;
|
||||
padding: ($unit * 1.5) ($unit * 2);
|
||||
width: 100%;
|
||||
|
||||
&.btn-disabled {
|
||||
background: $grey-90;
|
||||
color: $grey-70;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(.btn-disabled) {
|
||||
background: $grey-90;
|
||||
color: $grey-50;
|
||||
|
||||
&:hover {
|
||||
background: $grey-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.terms {
|
||||
color: $grey-50;
|
||||
font-size: $font-small;
|
||||
|
|
@ -40,8 +19,4 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
background: $grey-90;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -263,6 +263,15 @@ const SignupModal = (props: Props) => {
|
|||
})
|
||||
}
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function onOpenAutoFocus(event: Event) {
|
||||
event.preventDefault()
|
||||
if (usernameInput.current) usernameInput.current.focus()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogTrigger asChild>
|
||||
|
|
@ -270,7 +279,11 @@ const SignupModal = (props: Props) => {
|
|||
<span>{t('menu.signup')}</span>
|
||||
</li>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="Signup Dialog">
|
||||
<DialogContent
|
||||
className="Signup Dialog"
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTitle">
|
||||
<h1>{t('modals.signup.title')}</h1>
|
||||
|
|
@ -315,7 +328,10 @@ const SignupModal = (props: Props) => {
|
|||
ref={passwordConfirmationInput}
|
||||
/>
|
||||
|
||||
<Button text={t('modals.signup.buttons.confirm')} />
|
||||
<Button
|
||||
disabled={!formValid}
|
||||
text={t('modals.signup.buttons.confirm')}
|
||||
/>
|
||||
|
||||
<p className="terms">
|
||||
{/* <Trans i18nKey="modals.signup.agreement">
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import UncapStar from '~components/UncapStar'
|
||||
|
||||
import './index.scss'
|
||||
|
|
@ -10,12 +10,10 @@ interface Props {
|
|||
flb: boolean
|
||||
ulb: boolean
|
||||
special: boolean
|
||||
updateUncap?: (uncap: number) => void
|
||||
updateUncap?: (index: number) => void
|
||||
}
|
||||
|
||||
const UncapIndicator = (props: Props) => {
|
||||
const [uncap, setUncap] = useState(props.uncapLevel)
|
||||
|
||||
const numStars = setNumStars()
|
||||
function setNumStars() {
|
||||
let numStars
|
||||
|
|
@ -53,7 +51,7 @@ const UncapIndicator = (props: Props) => {
|
|||
|
||||
function toggleStar(index: number, empty: boolean) {
|
||||
if (props.updateUncap) {
|
||||
if (empty) props.updateUncap(index + 1)
|
||||
if (empty && index > 0) props.updateUncap(index + 1)
|
||||
else props.updateUncap(index)
|
||||
}
|
||||
}
|
||||
|
|
@ -71,10 +69,11 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
|
||||
const ulb = (i: number) => {
|
||||
// console.log('ULB; Number of stars:', props.uncapLevel)
|
||||
return (
|
||||
<UncapStar
|
||||
ulb={true}
|
||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
|
|
@ -83,10 +82,11 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
|
||||
const flb = (i: number) => {
|
||||
// console.log('FLB; Number of stars:', props.uncapLevel)
|
||||
return (
|
||||
<UncapStar
|
||||
flb={true}
|
||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
|
|
@ -95,10 +95,10 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
|
||||
const mlb = (i: number) => {
|
||||
// console.log("MLB; Number of stars:", props.uncapLevel)
|
||||
// console.log('MLB; Number of stars:', props.uncapLevel)
|
||||
return (
|
||||
<UncapStar
|
||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react'
|
||||
import React, { useEffect } from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import './index.scss'
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ const WeaponGrid = (props: Props) => {
|
|||
if (props.pushHistory) props.pushHistory(`/p/${party.shortcode}`)
|
||||
|
||||
saveWeapon(party.id, weapon, position).then((response) =>
|
||||
storeGridWeapon(response.data)
|
||||
storeGridWeapon(response.data.grid_weapon)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
|
|
@ -252,6 +252,7 @@ const WeaponGrid = (props: Props) => {
|
|||
)
|
||||
|
||||
const updateUncapLevel = (position: number, uncapLevel: number) => {
|
||||
console.log(`Updating uncap level at position ${position} to ${uncapLevel}`)
|
||||
if (appState.grid.weapons.mainWeapon && position == -1)
|
||||
appState.grid.weapons.mainWeapon.uncap_level = uncapLevel
|
||||
else {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,13 @@ import './index.scss'
|
|||
|
||||
// Props
|
||||
interface Props {
|
||||
open: boolean
|
||||
currentValue?: WeaponKey
|
||||
series: number
|
||||
slot: number
|
||||
onChange?: (value: string, slot: number) => void
|
||||
onOpenChange: () => void
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
||||
|
|
@ -64,10 +67,6 @@ const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
|||
fetchWeaponKeys()
|
||||
}, [props.series, props.slot])
|
||||
|
||||
function openSelect() {
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
function weaponKeyGroup(index: number) {
|
||||
;['α', 'β', 'γ', 'Δ'].sort((a, b) => a.localeCompare(b, 'el'))
|
||||
|
||||
|
|
@ -125,9 +124,10 @@ const WeaponKeySelect = React.forwardRef<HTMLButtonElement, Props>(
|
|||
<Select
|
||||
key={`weapon-key-${props.slot}`}
|
||||
value={props.currentValue ? props.currentValue.id : 'no-key'}
|
||||
open={open}
|
||||
open={props.open}
|
||||
onClose={props.onClose}
|
||||
onOpenChange={props.onOpenChange}
|
||||
onValueChange={handleChange}
|
||||
onClick={openSelect}
|
||||
ref={ref}
|
||||
triggerClass="modal"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,13 @@ import { useRouter } from 'next/router'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
import * as Dialog from '@radix-ui/react-dialog'
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '~components/Dialog'
|
||||
|
||||
import AXSelect from '~components/AxSelect'
|
||||
import AwakeningSelect from '~components/AwakeningSelect'
|
||||
|
|
@ -74,6 +80,14 @@ const WeaponModal = (props: Props) => {
|
|||
const [weaponKey2Id, setWeaponKey2Id] = useState('')
|
||||
const [weaponKey3Id, setWeaponKey3Id] = useState('')
|
||||
|
||||
const [weaponKey1Open, setWeaponKey1Open] = useState(false)
|
||||
const [weaponKey2Open, setWeaponKey2Open] = useState(false)
|
||||
const [weaponKey3Open, setWeaponKey3Open] = useState(false)
|
||||
const [weaponKey4Open, setWeaponKey4Open] = useState(false)
|
||||
const [ax1Open, setAx1Open] = useState(false)
|
||||
const [ax2Open, setAx2Open] = useState(false)
|
||||
const [awakeningOpen, setAwakeningOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setElement(props.gridWeapon.element)
|
||||
|
||||
|
|
@ -188,16 +202,35 @@ const WeaponModal = (props: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
function openSelect(index: 1 | 2 | 3 | 4) {
|
||||
setWeaponKey1Open(index === 1 ? !weaponKey1Open : false)
|
||||
setWeaponKey2Open(index === 2 ? !weaponKey2Open : false)
|
||||
setWeaponKey3Open(index === 3 ? !weaponKey3Open : false)
|
||||
setWeaponKey4Open(index === 4 ? !weaponKey4Open : false)
|
||||
}
|
||||
|
||||
function receiveAxOpen(index: 1 | 2, isOpen: boolean) {
|
||||
if (index === 1) setAx1Open(isOpen)
|
||||
if (index === 2) setAx2Open(isOpen)
|
||||
}
|
||||
|
||||
function receiveAwakeningOpen(isOpen: boolean) {
|
||||
setAwakeningOpen(isOpen)
|
||||
}
|
||||
|
||||
const keySelect = () => {
|
||||
return (
|
||||
<section>
|
||||
<h3>{t('modals.weapon.subtitles.weapon_keys')}</h3>
|
||||
{[2, 3, 17, 22].includes(props.gridWeapon.object.series) ? (
|
||||
<WeaponKeySelect
|
||||
open={weaponKey1Open}
|
||||
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={0}
|
||||
onOpenChange={() => openSelect(1)}
|
||||
onChange={receiveWeaponKey}
|
||||
onClose={() => setWeaponKey1Open(false)}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
|
|
@ -205,10 +238,13 @@ const WeaponModal = (props: Props) => {
|
|||
|
||||
{[2, 3, 17].includes(props.gridWeapon.object.series) ? (
|
||||
<WeaponKeySelect
|
||||
open={weaponKey2Open}
|
||||
currentValue={weaponKey2 != null ? weaponKey2 : undefined}
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={1}
|
||||
onOpenChange={() => openSelect(2)}
|
||||
onChange={receiveWeaponKey}
|
||||
onClose={() => setWeaponKey2Open(false)}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
|
|
@ -216,10 +252,13 @@ const WeaponModal = (props: Props) => {
|
|||
|
||||
{props.gridWeapon.object.series == 17 ? (
|
||||
<WeaponKeySelect
|
||||
open={weaponKey3Open}
|
||||
currentValue={weaponKey3 != null ? weaponKey3 : undefined}
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={2}
|
||||
onOpenChange={() => openSelect(3)}
|
||||
onChange={receiveWeaponKey}
|
||||
onClose={() => setWeaponKey3Open(false)}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
|
|
@ -228,10 +267,13 @@ const WeaponModal = (props: Props) => {
|
|||
{props.gridWeapon.object.series == 24 &&
|
||||
props.gridWeapon.object.uncap.ulb ? (
|
||||
<WeaponKeySelect
|
||||
open={weaponKey4Open}
|
||||
currentValue={weaponKey1 != null ? weaponKey1 : undefined}
|
||||
series={props.gridWeapon.object.series}
|
||||
slot={0}
|
||||
onOpenChange={() => openSelect(4)}
|
||||
onChange={receiveWeaponKey}
|
||||
onClose={() => setWeaponKey4Open(false)}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
|
|
@ -247,6 +289,7 @@ const WeaponModal = (props: Props) => {
|
|||
<AXSelect
|
||||
axType={props.gridWeapon.object.ax}
|
||||
currentSkills={props.gridWeapon.ax}
|
||||
onOpenChange={receiveAxOpen}
|
||||
sendValidity={receiveValidity}
|
||||
sendValues={receiveAxValues}
|
||||
/>
|
||||
|
|
@ -262,6 +305,7 @@ const WeaponModal = (props: Props) => {
|
|||
object="weapon"
|
||||
awakeningType={props.gridWeapon.awakening?.type}
|
||||
awakeningLevel={props.gridWeapon.awakening?.level}
|
||||
onOpenChange={receiveAwakeningOpen}
|
||||
sendValidity={receiveValidity}
|
||||
sendValues={receiveAwakeningValues}
|
||||
/>
|
||||
|
|
@ -278,49 +322,64 @@ const WeaponModal = (props: Props) => {
|
|||
setOpen(open)
|
||||
}
|
||||
|
||||
const anySelectOpen =
|
||||
weaponKey1Open ||
|
||||
weaponKey2Open ||
|
||||
weaponKey3Open ||
|
||||
weaponKey4Open ||
|
||||
ax1Open ||
|
||||
ax2Open ||
|
||||
awakeningOpen
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
if (anySelectOpen) {
|
||||
return event.preventDefault()
|
||||
} else {
|
||||
setOpen(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
// TODO: Refactor into Dialog component
|
||||
<Dialog.Root open={open} onOpenChange={openChange}>
|
||||
<Dialog.Trigger asChild>{props.children}</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Content
|
||||
className="Weapon Dialog"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTop">
|
||||
<Dialog.Title className="SubTitle">
|
||||
{t('modals.weapon.title')}
|
||||
</Dialog.Title>
|
||||
<Dialog.Title className="DialogTitle">
|
||||
{props.gridWeapon.object.name[locale]}
|
||||
</Dialog.Title>
|
||||
</div>
|
||||
<Dialog.Close className="DialogClose" asChild>
|
||||
<span>
|
||||
<CrossIcon />
|
||||
</span>
|
||||
</Dialog.Close>
|
||||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||
<DialogContent
|
||||
className="Weapon Dialog"
|
||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
>
|
||||
<div className="DialogHeader">
|
||||
<div className="DialogTop">
|
||||
<DialogTitle className="SubTitle">
|
||||
{t('modals.weapon.title')}
|
||||
</DialogTitle>
|
||||
<DialogTitle className="DialogTitle">
|
||||
{props.gridWeapon.object.name[locale]}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<DialogClose className="DialogClose" asChild>
|
||||
<span>
|
||||
<CrossIcon />
|
||||
</span>
|
||||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div className="mods">
|
||||
{props.gridWeapon.object.element == 0 ? elementSelect() : ''}
|
||||
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
||||
? keySelect()
|
||||
: ''}
|
||||
{props.gridWeapon.object.ax > 0 ? axSelect() : ''}
|
||||
{props.gridWeapon.awakening ? awakeningSelect() : ''}
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={updateWeapon}
|
||||
disabled={!formValid}
|
||||
text={t('modals.weapon.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</Dialog.Content>
|
||||
<Dialog.Overlay className="Overlay" />
|
||||
</Dialog.Portal>
|
||||
</Dialog.Root>
|
||||
<div className="mods">
|
||||
{props.gridWeapon.object.element == 0 ? elementSelect() : ''}
|
||||
{[2, 3, 17, 24].includes(props.gridWeapon.object.series)
|
||||
? keySelect()
|
||||
: ''}
|
||||
{props.gridWeapon.object.ax > 0 ? axSelect() : ''}
|
||||
{props.gridWeapon.awakening ? awakeningSelect() : ''}
|
||||
<Button
|
||||
contained={true}
|
||||
onClick={updateWeapon}
|
||||
disabled={!formValid}
|
||||
text={t('modals.weapon.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,33 +25,6 @@ const Proficiency = [
|
|||
'gun',
|
||||
'katana',
|
||||
]
|
||||
const Series = [
|
||||
'seraphic',
|
||||
'grand',
|
||||
'opus',
|
||||
'draconic',
|
||||
'revenant',
|
||||
'primal',
|
||||
'beast',
|
||||
'regalia',
|
||||
'omega',
|
||||
'olden_primal',
|
||||
'hollowsky',
|
||||
'xeno',
|
||||
'astral',
|
||||
'rose',
|
||||
'ultima',
|
||||
'bahamut',
|
||||
'epic',
|
||||
'ennead',
|
||||
'cosmos',
|
||||
'ancestral',
|
||||
'superlative',
|
||||
'vintage',
|
||||
'class_champion',
|
||||
'sephira',
|
||||
'new_world_foundation',
|
||||
]
|
||||
|
||||
const WeaponResult = (props: Props) => {
|
||||
const router = useRouter()
|
||||
|
|
|
|||
|
|
@ -15,12 +15,8 @@ import {
|
|||
emptyRarityState,
|
||||
emptyWeaponSeriesState,
|
||||
} from '~utils/emptyStates'
|
||||
import {
|
||||
elements,
|
||||
proficiencies,
|
||||
rarities,
|
||||
weaponSeries,
|
||||
} from '~utils/stateValues'
|
||||
import { elements, proficiencies, rarities } from '~utils/stateValues'
|
||||
import { weaponSeries } from '~utils/weaponSeries'
|
||||
|
||||
interface Props {
|
||||
sendFilters: (filters: { [key: string]: number[] }) => void
|
||||
|
|
@ -128,6 +124,45 @@ const WeaponSearchFilterBar = (props: Props) => {
|
|||
props.sendFilters(filters)
|
||||
}
|
||||
|
||||
const renderWeaponSeries = () => {
|
||||
const numColumns = 3
|
||||
return (
|
||||
<React.Fragment>
|
||||
{Array.from({ length: numColumns }, () => 0).map((x, i) => {
|
||||
return renderWeaponSeriesGroup(i)
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
const renderWeaponSeriesGroup = (index: number) => {
|
||||
return (
|
||||
<DropdownMenu.Group className="Group">
|
||||
{weaponSeries
|
||||
.slice(
|
||||
index * Math.ceil(weaponSeries.length / 3),
|
||||
(index + 1) * Math.ceil(weaponSeries.length / 3)
|
||||
)
|
||||
.map((x, i) => {
|
||||
return renderSingleWeaponSeries(x.id, x.slug)
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
)
|
||||
}
|
||||
|
||||
const renderSingleWeaponSeries = (id: number, slug: string) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={id}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={seriesState[slug].checked}
|
||||
valueKey={slug}
|
||||
>
|
||||
{t(`series.${slug}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
sendFilters()
|
||||
}, [rarityState, elementState, proficiencyState, seriesState])
|
||||
|
|
@ -254,58 +289,7 @@ const WeaponSearchFilterBar = (props: Props) => {
|
|||
<DropdownMenu.Label className="Label">
|
||||
{t('filters.labels.series')}
|
||||
</DropdownMenu.Label>
|
||||
<section>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={weaponSeries[i]}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={seriesState[weaponSeries[i]].checked}
|
||||
valueKey={weaponSeries[i]}
|
||||
>
|
||||
{t(`series.${weaponSeries[i]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={weaponSeries[i + weaponSeries.length / 3]}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={
|
||||
seriesState[weaponSeries[i + weaponSeries.length / 3]]
|
||||
.checked
|
||||
}
|
||||
valueKey={weaponSeries[i + weaponSeries.length / 3]}
|
||||
>
|
||||
{t(`series.${weaponSeries[i + weaponSeries.length / 3]}`)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
<DropdownMenu.Group className="Group">
|
||||
{Array.from(Array(weaponSeries.length / 3)).map((x, i) => {
|
||||
return (
|
||||
<SearchFilterCheckboxItem
|
||||
key={weaponSeries[i + 2 * (weaponSeries.length / 3)]}
|
||||
onCheckedChange={handleSeriesChange}
|
||||
checked={
|
||||
seriesState[weaponSeries[i + 2 * (weaponSeries.length / 3)]]
|
||||
.checked
|
||||
}
|
||||
valueKey={weaponSeries[i + 2 * (weaponSeries.length / 3)]}
|
||||
>
|
||||
{t(
|
||||
`series.${weaponSeries[i + 2 * (weaponSeries.length / 3)]}`
|
||||
)}
|
||||
</SearchFilterCheckboxItem>
|
||||
)
|
||||
})}
|
||||
</DropdownMenu.Group>
|
||||
</section>
|
||||
<section>{renderWeaponSeries()}</section>
|
||||
</SearchFilter>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import Button from '~components/Button'
|
|||
|
||||
import type { SearchableObject } from '~types'
|
||||
|
||||
import { appState } from '~utils/appState'
|
||||
import { axData } from '~utils/axData'
|
||||
import { weaponAwakening } from '~utils/awakening'
|
||||
|
||||
|
|
@ -341,9 +340,9 @@ const WeaponUnit = (props: Props) => {
|
|||
} else return
|
||||
}
|
||||
|
||||
function passUncapData(uncap: number) {
|
||||
function passUncapData(index: number) {
|
||||
if (props.gridWeapon)
|
||||
props.updateUncap(props.gridWeapon.id, props.position, uncap)
|
||||
props.updateUncap(props.gridWeapon.id, props.position, index)
|
||||
}
|
||||
|
||||
function canBeModified(gridWeapon: GridWeapon) {
|
||||
|
|
|
|||
|
|
@ -246,15 +246,27 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
return (
|
||||
<div id="Profile">
|
||||
<Head>
|
||||
<title>@{props.user?.username}'s Teams</title>
|
||||
{/* HTML */}
|
||||
<title>
|
||||
{t('page.titles.profile', { username: props.user?.username })}
|
||||
</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={t('page.descriptions.profile', {
|
||||
username: props.user?.username,
|
||||
})}
|
||||
/>
|
||||
|
||||
{/* OpenGraph */}
|
||||
<meta
|
||||
property="og:title"
|
||||
content={`@${props.user?.username}\'s Teams`}
|
||||
content={t('page.titles.profile', { username: props.user?.username })}
|
||||
/>
|
||||
<meta
|
||||
property="og:description"
|
||||
content={`Browse @${props.user?.username}\'s Teams and filter raid, element or recency`}
|
||||
content={t('page.descriptions.profile', {
|
||||
username: props.user?.username,
|
||||
})}
|
||||
/>
|
||||
<meta
|
||||
property="og:url"
|
||||
|
|
@ -262,15 +274,18 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
|||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
{/* Twitter */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="app.granblue.team" />
|
||||
<meta
|
||||
name="twitter:title"
|
||||
content={`@${props.user?.username}\'s Teams`}
|
||||
content={t('page.titles.profile', { username: props.user?.username })}
|
||||
/>
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content={`Browse @${props.user?.username}\''s Teams and filter raid, element or recency`}
|
||||
content={t('page.descriptions.profile', {
|
||||
username: props.user?.username,
|
||||
})}
|
||||
/>
|
||||
</Head>
|
||||
<FilterBar
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import React, { useEffect } from 'react'
|
||||
import { getCookie } from 'cookies-next'
|
||||
import Head from 'next/head'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
||||
import Party from '~components/Party'
|
||||
|
|
@ -12,6 +13,9 @@ import api from '~utils/api'
|
|||
|
||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { GroupedWeaponKeys } from '~utils/groupWeaponKeys'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { raidGroups } from '~utils/raidGroups'
|
||||
import { useRouter } from 'next/router'
|
||||
|
||||
interface Props {
|
||||
party: Party
|
||||
|
|
@ -20,9 +24,18 @@ interface Props {
|
|||
raids: Raid[]
|
||||
sortedRaids: Raid[][]
|
||||
weaponKeys: GroupedWeaponKeys
|
||||
meta: { [key: string]: string }
|
||||
}
|
||||
|
||||
const PartyRoute: React.FC<Props> = (props: Props) => {
|
||||
// Import translations
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const locale =
|
||||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||
|
||||
useEffect(() => {
|
||||
persistStaticData()
|
||||
}, [persistStaticData])
|
||||
|
|
@ -34,7 +47,65 @@ const PartyRoute: React.FC<Props> = (props: Props) => {
|
|||
appState.weaponKeys = props.weaponKeys
|
||||
}
|
||||
|
||||
return <Party team={props.party} raids={props.sortedRaids} />
|
||||
function generateTitle() {
|
||||
const teamName =
|
||||
props.party && props.party.name ? props.party.name : t('no_title')
|
||||
const username = props.party
|
||||
? `@${props.party.user.username}`
|
||||
: t('no_user')
|
||||
|
||||
const title = t('page.titles.team', {
|
||||
username: username,
|
||||
teamName: teamName,
|
||||
emoji: props.meta.element,
|
||||
})
|
||||
|
||||
return title
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Party team={props.party} raids={props.sortedRaids} />
|
||||
<Head>
|
||||
{/* HTML */}
|
||||
<title>{generateTitle()}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content={t('page.descriptions.team', {
|
||||
username: props.party.user?.username,
|
||||
raidName: props.party.raid.name[locale],
|
||||
})}
|
||||
/>
|
||||
|
||||
{/* OpenGraph */}
|
||||
<meta property="og:title" content={generateTitle()} />
|
||||
<meta
|
||||
property="og:description"
|
||||
content={t('page.descriptions.team', {
|
||||
username: props.party.user?.username,
|
||||
raidName: props.party.raid.name[locale],
|
||||
})}
|
||||
/>
|
||||
<meta
|
||||
property="og:url"
|
||||
content={`https://app.granblue.team/p/${props.party.shortcode}`}
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
{/* Twitter */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="app.granblue.team" />
|
||||
<meta name="twitter:title" content={generateTitle()} />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content={t('page.descriptions.team', {
|
||||
username: props.party.user?.username,
|
||||
raidName: props.party.raid.name[locale],
|
||||
})}
|
||||
/>
|
||||
</Head>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSidePaths = async () => {
|
||||
|
|
@ -80,6 +151,31 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
|||
console.log('No party code')
|
||||
}
|
||||
|
||||
function getElement() {
|
||||
if (party) {
|
||||
const mainhand = party.weapons.find((weapon) => weapon.mainhand)
|
||||
if (mainhand && mainhand.object.element === 0) {
|
||||
return mainhand.element
|
||||
} else {
|
||||
return mainhand?.object.element
|
||||
}
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
function elementEmoji() {
|
||||
const element = getElement()
|
||||
|
||||
if (element === 0) return '⚪'
|
||||
if (element === 1) return '🟢'
|
||||
if (element === 2) return '🔴'
|
||||
if (element === 3) return '🔵'
|
||||
if (element === 4) return '🟤'
|
||||
if (element === 5) return '🟣'
|
||||
if (element === 6) return '🟡'
|
||||
}
|
||||
|
||||
return {
|
||||
props: {
|
||||
party: party,
|
||||
|
|
@ -88,6 +184,9 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
|||
raids: raids,
|
||||
sortedRaids: sortedRaids,
|
||||
weaponKeys: weaponKeys,
|
||||
meta: {
|
||||
element: elementEmoji()
|
||||
},
|
||||
...(await serverSideTranslations(locale, ['common'])),
|
||||
// Will be passed to the page component as props
|
||||
},
|
||||
|
|
|
|||
|
|
@ -288,15 +288,15 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
|||
return (
|
||||
<div id="Teams">
|
||||
<Head>
|
||||
<title>{t('saved.title')}</title>
|
||||
<title>{t('page.titles.saved')}</title>
|
||||
|
||||
<meta property="og:title" content="Your saved Teams" />
|
||||
<meta property="og:title" content={t('page.titles.saved')} />
|
||||
<meta property="og:url" content="https://app.granblue.team/saved" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="app.granblue.team" />
|
||||
<meta name="twitter:title" content="Your saved Teams" />
|
||||
<meta name="twitter:title" content={t('page.titles.saved')} />
|
||||
</Head>
|
||||
|
||||
<FilterBar
|
||||
|
|
|
|||
|
|
@ -287,22 +287,26 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
|||
return (
|
||||
<div id="Teams">
|
||||
<Head>
|
||||
<title>{t('teams.title')}</title>
|
||||
{/* HTML */}
|
||||
<title>{t('page.titles.discover')}</title>
|
||||
<meta name="description" content={t('page.descriptions.discover')} />
|
||||
|
||||
<meta property="og:title" content="Discover Teams" />
|
||||
{/* OpenGraph */}
|
||||
<meta property="og:title" content={t('page.titles.discover')} />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Find different Granblue Fantasy teams by raid, element or recency"
|
||||
content={t('page.descriptions.discover')}
|
||||
/>
|
||||
<meta property="og:url" content="https://app.granblue.team/teams" />
|
||||
<meta property="og:type" content="website" />
|
||||
|
||||
{/* Twitter */}
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="app.granblue.team" />
|
||||
<meta name="twitter:title" content="Discover Teams" />
|
||||
<meta name="twitter:title" content={t('page.titles.discover')} />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Find different Granblue Fantasy teams by raid, element or recency"
|
||||
content={t('page.descriptions.discover')}
|
||||
/>
|
||||
</Head>
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
"bahamut": "Bahamut",
|
||||
"epic": "Epic",
|
||||
"ennead": "Ennead",
|
||||
"cosmos": "Cosmos",
|
||||
"cosmic": "Cosmic",
|
||||
"ancestral": "Ancestral",
|
||||
"superlative": "Superlative",
|
||||
"vintage": "Vintage",
|
||||
|
|
@ -109,7 +109,8 @@
|
|||
"new_world": "New World Foundation",
|
||||
"revenant": "Revenant",
|
||||
"proving": "Proving Grounds",
|
||||
"disaster": "Disaster"
|
||||
"disaster": "Disaster",
|
||||
"illustrious": "Illustrious"
|
||||
},
|
||||
"recency": {
|
||||
"all_time": "All time",
|
||||
|
|
@ -277,6 +278,19 @@
|
|||
"loading": "Loading teams...",
|
||||
"not_found": "No teams found"
|
||||
},
|
||||
"page": {
|
||||
"titles": {
|
||||
"discover": "Discover teams",
|
||||
"profile": "@{{username}}'s Teams / granblue.team",
|
||||
"team": "{{emoji}} {{teamName}} by {{username}} / granblue.team",
|
||||
"saved": "Your saved teams"
|
||||
},
|
||||
"descriptions": {
|
||||
"discover": "Save and discover teams to use in Granblue Fantasy and search by raid, element or recency",
|
||||
"profile": "Browse @{{username}}'s Teams and filter by raid, element or recency",
|
||||
"team": "Browse this team for {{raidName}} by {{username}} and others on granblue.team"
|
||||
}
|
||||
},
|
||||
"job_skills": {
|
||||
"all": "All skills",
|
||||
"buffing": "Buffing",
|
||||
|
|
@ -294,5 +308,6 @@
|
|||
"coming_soon": "Coming Soon",
|
||||
"no_title": "Untitled",
|
||||
"no_raid": "No raid",
|
||||
"no_user": "Anonymous"
|
||||
"no_user": "Anonymous",
|
||||
"no_job": "No class"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@
|
|||
"bahamut": "バハムートウェポン",
|
||||
"epic": "エピックウェポン",
|
||||
"ennead": "エニアドシリーズ",
|
||||
"cosmos": "コスモスシリーズ",
|
||||
"cosmic": "コスモスシリーズ",
|
||||
"ancestral": "アンセスタルシリーズ",
|
||||
"superlative": "スペリオシリーズ",
|
||||
"vintage": "ヴィンテージシリーズ",
|
||||
|
|
@ -109,7 +109,8 @@
|
|||
"new_world": "新世界の礎",
|
||||
"revenant": "天星器",
|
||||
"proving": "ブレイブグラウンド",
|
||||
"disaster": "災害シリーズ"
|
||||
"disaster": "災害シリーズ",
|
||||
"luminous": "ルミナス"
|
||||
},
|
||||
"recency": {
|
||||
"all_time": "全ての期間",
|
||||
|
|
@ -278,6 +279,19 @@
|
|||
"loading": "ロード中...",
|
||||
"not_found": "編成は見つかりませんでした"
|
||||
},
|
||||
"page": {
|
||||
"titles": {
|
||||
"discover": "編成を見出す",
|
||||
"profile": "@{{username}}さんの作った編成",
|
||||
"team": "{{emoji}} {{teamName}}、{{username}}さんから / granblue.team",
|
||||
"saved": "保存した編成"
|
||||
},
|
||||
"descriptions": {
|
||||
"discover": "グランブルーファンタジーの編成をマルチ、属性、作った時間などで探したり保存したりできる",
|
||||
"profile": "@{{username}}の編成を調査し、マルチ、属性、または作った時間でフィルターする",
|
||||
"team": "granblue.teamで{{username}}さんが作った{{raidName}}の編成を調査できる"
|
||||
}
|
||||
},
|
||||
"job_skills": {
|
||||
"all": "全てのアビリティ",
|
||||
"buffing": "強化アビリティ",
|
||||
|
|
@ -291,9 +305,10 @@
|
|||
"no_skill": "設定されていません"
|
||||
}
|
||||
},
|
||||
"extra_weapons": "Additional<br/>Weapons",
|
||||
"extra_weapons": "Additional Weapons",
|
||||
"coming_soon": "開発中",
|
||||
"no_title": "無題",
|
||||
"no_raid": "マルチなし",
|
||||
"no_user": "無名"
|
||||
"no_user": "無名",
|
||||
"no_job": "ジョブなし"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,27 +33,27 @@ a {
|
|||
text-decoration: none;
|
||||
|
||||
&.wind {
|
||||
color: $wind-text-10;
|
||||
color: var(--wind-bg);
|
||||
}
|
||||
|
||||
&.fire {
|
||||
color: $fire-text-10;
|
||||
color: var(--fire-bg);
|
||||
}
|
||||
|
||||
&.water {
|
||||
color: $water-text-10;
|
||||
color: var(--water-bg);
|
||||
}
|
||||
|
||||
&.earth {
|
||||
color: $earth-text-10;
|
||||
color: var(--earth-bg);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
color: $dark-text-10;
|
||||
color: var(--dark-bg);
|
||||
}
|
||||
|
||||
&.light {
|
||||
color: $light-text-10;
|
||||
color: var(--light-bg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ h1,
|
|||
h2,
|
||||
h3,
|
||||
p {
|
||||
color: $grey-15;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
h1 {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
--card-bg: #{$grey-100};
|
||||
--bar-bg: #{$grey-100};
|
||||
|
||||
--link-text-hover: #{$text--link--hover--light};
|
||||
|
||||
// Light - Menus
|
||||
--dialog-bg: #{$dialog--bg--light};
|
||||
|
||||
|
|
@ -120,6 +122,8 @@
|
|||
--card-bg: #{$page--element--bg--dark};
|
||||
--bar-bg: #{$grey-10};
|
||||
|
||||
--link-text-hover: #{$text--link--hover--dark};
|
||||
|
||||
// Dark - Dialogs
|
||||
--dialog-bg: #{$dialog--bg--dark};
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ $orange-10: #6b401b;
|
|||
$orange-30: #825b39;
|
||||
$orange-40: #925a2a;
|
||||
$orange-50: #a8703f;
|
||||
$orange-70: #d08f57;
|
||||
$orange-80: #facea7;
|
||||
$orange-90: #ffebd9;
|
||||
|
||||
|
|
@ -213,7 +214,7 @@ $subaura--orange--bg--dark: $orange-10;
|
|||
$subaura--orange--card--bg--dark: $orange-30;
|
||||
$subaura--orange--primary--dark: $orange-00;
|
||||
$subaura--orange--secondary--dark: $orange-10;
|
||||
$subaura--orange--text--dark: $orange-00;
|
||||
$subaura--orange--text--dark: $orange-70;
|
||||
|
||||
// Color Definitions: Element Toggle
|
||||
$toggle--bg--light: $grey-90;
|
||||
|
|
@ -235,6 +236,9 @@ $text--tertiary--color--dark: $grey-50;
|
|||
$text--tertiary--hover--light: $grey-40;
|
||||
$text--tertiary--hover--dark: $grey-70;
|
||||
|
||||
$text--link--hover--light: $grey-40;
|
||||
$text--link--hover--dark: $grey-100;
|
||||
|
||||
// Color Definitions: Icon
|
||||
$icon--secondary--color--light: $grey-70;
|
||||
$icon--secondary--color--dark: $grey-50;
|
||||
|
|
|
|||
2
types/WeaponSeries.d.ts
vendored
2
types/WeaponSeries.d.ts
vendored
|
|
@ -17,7 +17,7 @@ interface WeaponSeriesState {
|
|||
astral: CheckedState
|
||||
epic: CheckedState
|
||||
ennead: CheckedState
|
||||
cosmos: CheckedState
|
||||
cosmic: CheckedState
|
||||
ancestral: CheckedState
|
||||
superlative: CheckedState
|
||||
vintage: CheckedState
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ export const initialAppState: AppState = {
|
|||
extra: false,
|
||||
user: undefined,
|
||||
favorited: false,
|
||||
created_at: new Date().toISOString(),
|
||||
updated_at: new Date().toISOString(),
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
},
|
||||
grid: {
|
||||
weapons: {
|
||||
|
|
|
|||
|
|
@ -100,12 +100,16 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
|||
id: 3,
|
||||
checked: false,
|
||||
},
|
||||
ultima: {
|
||||
id: 17,
|
||||
revenant: {
|
||||
id: 4,
|
||||
checked: false,
|
||||
},
|
||||
bahamut: {
|
||||
id: 16,
|
||||
primal: {
|
||||
id: 6,
|
||||
checked: false,
|
||||
},
|
||||
beast: {
|
||||
id: 7,
|
||||
checked: false,
|
||||
},
|
||||
regalia: {
|
||||
|
|
@ -116,10 +120,6 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
|||
id: 9,
|
||||
checked: false,
|
||||
},
|
||||
primal: {
|
||||
id: 6,
|
||||
checked: false,
|
||||
},
|
||||
olden_primal: {
|
||||
id: 10,
|
||||
checked: false,
|
||||
|
|
@ -128,26 +128,30 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
|||
id: 11,
|
||||
checked: false,
|
||||
},
|
||||
beast: {
|
||||
id: 7,
|
||||
checked: false,
|
||||
},
|
||||
rose: {
|
||||
id: 15,
|
||||
hollowsky: {
|
||||
id: 12,
|
||||
checked: false,
|
||||
},
|
||||
xeno: {
|
||||
id: 13,
|
||||
checked: false,
|
||||
},
|
||||
hollowsky: {
|
||||
id: 12,
|
||||
checked: false,
|
||||
},
|
||||
astral: {
|
||||
id: 14,
|
||||
checked: false,
|
||||
},
|
||||
rose: {
|
||||
id: 15,
|
||||
checked: false,
|
||||
},
|
||||
bahamut: {
|
||||
id: 16,
|
||||
checked: false,
|
||||
},
|
||||
ultima: {
|
||||
id: 17,
|
||||
checked: false,
|
||||
},
|
||||
epic: {
|
||||
id: 18,
|
||||
checked: false,
|
||||
|
|
@ -156,7 +160,7 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
|||
id: 19,
|
||||
checked: false,
|
||||
},
|
||||
cosmos: {
|
||||
cosmic: {
|
||||
id: 20,
|
||||
checked: false,
|
||||
},
|
||||
|
|
@ -176,6 +180,10 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
|||
id: 24,
|
||||
checked: false,
|
||||
},
|
||||
proving: {
|
||||
id: 25,
|
||||
checked: false,
|
||||
},
|
||||
sephira: {
|
||||
id: 28,
|
||||
checked: false,
|
||||
|
|
@ -184,6 +192,14 @@ export const emptyWeaponSeriesState: WeaponSeriesState = {
|
|||
id: 29,
|
||||
checked: false,
|
||||
},
|
||||
disaster: {
|
||||
id: 30,
|
||||
checked: false,
|
||||
},
|
||||
illustrious: {
|
||||
id: 31,
|
||||
checked: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const emptyPaginationObject = {
|
||||
|
|
|
|||
|
|
@ -116,4 +116,8 @@ export const weaponSeries: WeaponSeries[] = [
|
|||
id: 30,
|
||||
slug: 'disaster',
|
||||
},
|
||||
{
|
||||
id: 31,
|
||||
slug: 'illustrious',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
Loading…
Reference in a new issue