Add new fields to parties
I forgot to commit
This commit is contained in:
parent
9e2b7f2dd7
commit
54dd3feba7
26 changed files with 929 additions and 99 deletions
|
|
@ -8,43 +8,6 @@
|
|||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit * 2;
|
||||
|
||||
.Switch {
|
||||
$height: 34px;
|
||||
background: $grey-70;
|
||||
border-radius: calc($height / 2);
|
||||
border: none;
|
||||
position: relative;
|
||||
width: 58px;
|
||||
height: $height;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px $grey-15;
|
||||
}
|
||||
|
||||
&[data-state='checked'] {
|
||||
background: $grey-15;
|
||||
}
|
||||
}
|
||||
|
||||
.Thumb {
|
||||
background: $grey-100;
|
||||
border-radius: 13px;
|
||||
display: block;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
transition: transform 100ms;
|
||||
transform: translateX(-1px);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&[data-state='checked'] {
|
||||
background: $grey-100;
|
||||
transform: translateX(21px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.DialogDescription {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import React, { ForwardedRef, useEffect, useState } from 'react'
|
|||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
import Input from '~components/Input'
|
||||
import Input from '~components/LabelledInput'
|
||||
import Select from '~components/Select'
|
||||
import SelectItem from '~components/SelectItem'
|
||||
|
||||
|
|
@ -187,7 +187,7 @@ const AwakeningSelect = (props: Props) => {
|
|||
max={maxValue}
|
||||
step="1"
|
||||
onChange={handleInputChange}
|
||||
visible={`${awakeningType !== -1}`}
|
||||
visible={awakeningType !== -1 ? true : false}
|
||||
ref={awakeningLevelInput}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,6 @@
|
|||
// box-shadow: 0 2px rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
.Input {
|
||||
padding: $unit * 1.5 $unit-2x;
|
||||
}
|
||||
|
||||
.Counter {
|
||||
color: $grey-55;
|
||||
font-weight: $bold;
|
||||
|
|
@ -29,10 +25,13 @@
|
|||
|
||||
.Input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: $unit * 1.5 $unit-2x;
|
||||
padding-left: calc($unit-2x - $offset);
|
||||
|
||||
&:focus {
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
0
components/DurationInput/index.scss
Normal file
0
components/DurationInput/index.scss
Normal file
166
components/DurationInput/index.tsx
Normal file
166
components/DurationInput/index.tsx
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
import React, { useState, ChangeEvent, KeyboardEvent, useEffect } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import Input from '~components/Input'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
> {
|
||||
value: number
|
||||
onValueChange: (value: number) => void
|
||||
}
|
||||
|
||||
const DurationInput = React.forwardRef<HTMLInputElement, Props>(
|
||||
function DurationInput(
|
||||
{ className, placeholder, value, onValueChange },
|
||||
forwardedRef
|
||||
) {
|
||||
const [duration, setDuration] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (value > 0) setDuration(convertSecondsToString(value))
|
||||
}, [value])
|
||||
|
||||
function convertStringToSeconds(string: string) {
|
||||
const parts = string.split(':')
|
||||
const minutes = parseInt(parts[0])
|
||||
const seconds = parseInt(parts[1])
|
||||
|
||||
return minutes * 60 + seconds
|
||||
}
|
||||
|
||||
function convertSecondsToString(value: number) {
|
||||
const minutes = Math.floor(value / 60)
|
||||
const seconds = value - minutes * 60
|
||||
|
||||
const paddedMinutes = padNumber(`${minutes}`, '0', 2)
|
||||
|
||||
return `${paddedMinutes}:${seconds}`
|
||||
}
|
||||
|
||||
function padNumber(string: string, pad: string, length: number) {
|
||||
return (new Array(length + 1).join(pad) + string).slice(-length)
|
||||
}
|
||||
|
||||
function handleChange(event: ChangeEvent<HTMLInputElement>) {
|
||||
const value = event.currentTarget.value
|
||||
const durationInSeconds = convertStringToSeconds(value)
|
||||
onValueChange(durationInSeconds)
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
// Allow the key to be processed normally
|
||||
return
|
||||
}
|
||||
|
||||
// Get the current value
|
||||
const input = event.currentTarget
|
||||
let value = event.currentTarget.value
|
||||
|
||||
// Check if the key that was pressed is the backspace key
|
||||
if (event.key === 'Backspace') {
|
||||
// Remove the colon if the value is "12:"
|
||||
if (value.length === 4) {
|
||||
value = value.slice(0, -1)
|
||||
}
|
||||
|
||||
// Allow the backspace key to be processed normally
|
||||
input.value = value
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the key that was pressed is the tab key
|
||||
if (event.key === 'Tab') {
|
||||
// Allow the tab key to be processed normally
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the key that was pressed is an arrow key
|
||||
if (event.key === 'ArrowUp') {
|
||||
// Step the value up by one
|
||||
value = incrementTime(value)
|
||||
} else if (event.key === 'ArrowDown') {
|
||||
// Step the value down by one
|
||||
value = decrementTime(value)
|
||||
} else {
|
||||
// Get the character that was entered
|
||||
const char = parseInt(event.key)
|
||||
|
||||
// Check if the character is a number
|
||||
const isNumber = !isNaN(char)
|
||||
|
||||
// Check if the character should be accepted or rejected
|
||||
if (!isNumber || value.length >= 5) {
|
||||
// Reject the character
|
||||
event.preventDefault()
|
||||
} else if (value.length === 2) {
|
||||
// Insert a colon after the second digit
|
||||
input.value = value + ':'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function incrementTime(time: string): string {
|
||||
// Split the time into minutes and seconds
|
||||
let [minutes, seconds] = time.split(':').map(Number)
|
||||
|
||||
// Increment the seconds
|
||||
seconds += 1
|
||||
|
||||
// Check if the seconds have overflowed into the next minute
|
||||
if (seconds >= 60) {
|
||||
minutes += 1
|
||||
seconds = 0
|
||||
}
|
||||
|
||||
// Format the time as a string and return it
|
||||
return `${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
function decrementTime(time: string): string {
|
||||
// Split the time into minutes and seconds
|
||||
let [minutes, seconds] = time.split(':').map(Number)
|
||||
|
||||
// Decrement the seconds
|
||||
seconds -= 1
|
||||
|
||||
// Check if the seconds have underflowed into the previous minute
|
||||
if (seconds < 0) {
|
||||
minutes -= 1
|
||||
seconds = 59
|
||||
}
|
||||
|
||||
// Check if the minutes have underflowed into the previous hour
|
||||
if (minutes < 0) {
|
||||
minutes = 59
|
||||
}
|
||||
|
||||
// Format the time as a string and return it
|
||||
return `${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
type="text"
|
||||
className={classNames(
|
||||
{
|
||||
Duration: true,
|
||||
AlignRight: true,
|
||||
},
|
||||
className
|
||||
)}
|
||||
value={duration}
|
||||
onChange={handleChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default DurationInput
|
||||
|
|
@ -1,19 +1,22 @@
|
|||
.Label {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Input {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
background-color: var(--input-bg);
|
||||
border: none;
|
||||
border: 2px solid transparent;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
padding: $unit-2x;
|
||||
width: 100%;
|
||||
|
||||
&[type='number']::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
border: 2px solid $blue;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&.Bound {
|
||||
background-color: var(--input-bound-bg);
|
||||
|
||||
|
|
@ -22,6 +25,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.AlignRight {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
&.Hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,13 +39,7 @@ const Input = React.forwardRef<HTMLInputElement, Props>(function Input(
|
|||
}
|
||||
|
||||
return (
|
||||
<label
|
||||
className={classNames({
|
||||
Label: true,
|
||||
Visible: props.visible,
|
||||
})}
|
||||
htmlFor={props.name}
|
||||
>
|
||||
<React.Fragment>
|
||||
<input
|
||||
{...inputProps}
|
||||
autoComplete="off"
|
||||
|
|
@ -55,11 +49,10 @@ const Input = React.forwardRef<HTMLInputElement, Props>(function Input(
|
|||
onChange={handleChange}
|
||||
formNoValidate
|
||||
/>
|
||||
{props.label}
|
||||
{props.error && props.error.length > 0 && (
|
||||
<p className="InputError">{props.error}</p>
|
||||
)}
|
||||
</label>
|
||||
</React.Fragment>
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
5
components/LabelledInput/index.scss
Normal file
5
components/LabelledInput/index.scss
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
.Label {
|
||||
box-sizing: border-box;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
}
|
||||
68
components/LabelledInput/index.tsx
Normal file
68
components/LabelledInput/index.tsx
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
> {
|
||||
visible?: boolean
|
||||
error?: string
|
||||
label?: string
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
visible: true,
|
||||
}
|
||||
|
||||
const LabelledInput = React.forwardRef<HTMLInputElement, Props>(function Input(
|
||||
props: Props,
|
||||
forwardedRef
|
||||
) {
|
||||
// States
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
// Classes
|
||||
const classes = classNames({ Input: true }, props.className)
|
||||
const { defaultValue, ...inputProps } = props
|
||||
|
||||
// Change value when prop updates
|
||||
useEffect(() => {
|
||||
if (props.value) setInputValue(`${props.value}`)
|
||||
}, [props.value])
|
||||
|
||||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setInputValue(event.target.value)
|
||||
if (props.onChange) props.onChange(event)
|
||||
}
|
||||
|
||||
return (
|
||||
<label
|
||||
className={classNames({
|
||||
Label: true,
|
||||
Visible: props.visible,
|
||||
})}
|
||||
htmlFor={props.name}
|
||||
>
|
||||
<input
|
||||
{...inputProps}
|
||||
autoComplete="off"
|
||||
className={classes}
|
||||
value={inputValue}
|
||||
ref={forwardedRef}
|
||||
onChange={handleChange}
|
||||
formNoValidate
|
||||
/>
|
||||
{props.label}
|
||||
{props.error && props.error.length > 0 && (
|
||||
<p className="InputError">{props.error}</p>
|
||||
)}
|
||||
</label>
|
||||
)
|
||||
})
|
||||
|
||||
LabelledInput.defaultProps = defaultProps
|
||||
|
||||
export default LabelledInput
|
||||
|
|
@ -9,7 +9,7 @@ import setUserToken from '~utils/setUserToken'
|
|||
import { accountState } from '~utils/accountState'
|
||||
|
||||
import Button from '~components/Button'
|
||||
import Input from '~components/Input'
|
||||
import Input from '~components/LabelledInput'
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { getCookie } from 'cookies-next'
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
|
||||
import PartySegmentedControl from '~components/PartySegmentedControl'
|
||||
|
|
@ -13,6 +12,7 @@ import CharacterGrid from '~components/CharacterGrid'
|
|||
import api from '~utils/api'
|
||||
import { appState, initialAppState } from '~utils/appState'
|
||||
import { GridType, TeamElement } from '~utils/enums'
|
||||
import type { DetailsObject } from '~types'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
|
|
@ -59,25 +59,42 @@ const Party = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
function updateDetails(name?: string, description?: string, raid?: Raid) {
|
||||
function updateDetails(details: DetailsObject) {
|
||||
if (
|
||||
appState.party.name !== name ||
|
||||
appState.party.description !== description ||
|
||||
appState.party.raid?.id !== raid?.id
|
||||
appState.party.name !== details.name ||
|
||||
appState.party.description !== details.description ||
|
||||
appState.party.raid?.id !== details.raid?.id
|
||||
) {
|
||||
if (appState.party.id)
|
||||
api.endpoints.parties
|
||||
.update(appState.party.id, {
|
||||
party: {
|
||||
name: name,
|
||||
description: description,
|
||||
raid_id: raid?.id,
|
||||
name: details.name,
|
||||
description: details.description,
|
||||
raid_id: details.raid?.id,
|
||||
charge_attack: details.chargeAttack,
|
||||
full_auto: details.fullAuto,
|
||||
auto_guard: details.autoGuard,
|
||||
clear_time: details.clearTime,
|
||||
button_count: details.buttonCount,
|
||||
chain_count: details.chainCount,
|
||||
turn_count: details.turnCount,
|
||||
},
|
||||
})
|
||||
.then(() => {
|
||||
appState.party.name = name
|
||||
appState.party.description = description
|
||||
appState.party.raid = raid
|
||||
appState.party.name = details.name
|
||||
appState.party.description = details.description
|
||||
appState.party.raid = details.raid
|
||||
|
||||
appState.party.chargeAttack = details.chargeAttack
|
||||
appState.party.fullAuto = details.fullAuto
|
||||
appState.party.autoGuard = details.autoGuard
|
||||
|
||||
appState.party.clearTime = details.clearTime
|
||||
appState.party.buttonCount = details.buttonCount
|
||||
appState.party.chainCount = details.chainCount
|
||||
appState.party.turnCount = details.turnCount
|
||||
|
||||
appState.party.updated_at = party.updated_at
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,94 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.DetailToggleGroup {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: $unit;
|
||||
|
||||
.ToggleSection,
|
||||
.InputSection {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background: var(--card-bg);
|
||||
border-radius: $card-corner;
|
||||
|
||||
& > label {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: $font-regular;
|
||||
gap: $unit;
|
||||
grid-template-columns: 2fr 1fr;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
|
||||
& > span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ToggleSection {
|
||||
padding: ($unit * 1.5) $unit-2x;
|
||||
}
|
||||
|
||||
.InputSection {
|
||||
padding: $unit-half $unit-2x;
|
||||
padding-right: $unit-half;
|
||||
|
||||
.Input {
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
div.Input {
|
||||
align-items: center;
|
||||
border: 2px solid transparent;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
padding: $unit;
|
||||
|
||||
&:has(> input:focus) {
|
||||
border: 2px solid $blue;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
& > input {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: $unit 0;
|
||||
text-align: right;
|
||||
width: 2rem;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
span {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.Input {
|
||||
border-radius: 7px;
|
||||
max-width: 10rem;
|
||||
}
|
||||
|
||||
div {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit-half;
|
||||
justify-content: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
@ -75,6 +163,13 @@
|
|||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.Details {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit-half;
|
||||
margin-bottom: $unit-2x;
|
||||
}
|
||||
|
||||
.YoutubeWrapper {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: $card-corner;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
|
|
@ -8,32 +8,38 @@ import Linkify from 'react-linkify'
|
|||
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
|
||||
import classNames from 'classnames'
|
||||
import reactStringReplace from 'react-string-replace'
|
||||
import sanitizeHtml from 'sanitize-html'
|
||||
|
||||
import * as AlertDialog from '@radix-ui/react-alert-dialog'
|
||||
|
||||
import Button from '~components/Button'
|
||||
import CharLimitedFieldset from '~components/CharLimitedFieldset'
|
||||
import Input from '~components/Input'
|
||||
import DurationInput from '~components/DurationInput'
|
||||
import Token from '~components/Token'
|
||||
|
||||
import RaidDropdown from '~components/RaidDropdown'
|
||||
import TextFieldset from '~components/TextFieldset'
|
||||
import Switch from '~components/Switch'
|
||||
|
||||
import { accountState } from '~utils/accountState'
|
||||
import { appState } from '~utils/appState'
|
||||
import { formatTimeAgo } from '~utils/timeAgo'
|
||||
import { youtube } from '~utils/youtube'
|
||||
|
||||
import CheckIcon from '~public/icons/Check.svg'
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import EditIcon from '~public/icons/Edit.svg'
|
||||
|
||||
import type { DetailsObject } from 'types'
|
||||
|
||||
import './index.scss'
|
||||
import { youtube } from '~utils/youtube'
|
||||
|
||||
// Props
|
||||
interface Props {
|
||||
party?: Party
|
||||
new: boolean
|
||||
editable: boolean
|
||||
updateCallback: (name?: string, description?: string, raid?: Raid) => void
|
||||
updateCallback: (details: DetailsObject) => void
|
||||
deleteCallback: (
|
||||
event: React.MouseEvent<HTMLButtonElement, MouseEvent>
|
||||
) => void
|
||||
|
|
@ -53,6 +59,17 @@ const PartyDetails = (props: Props) => {
|
|||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState('')
|
||||
|
||||
const [chargeAttack, setChargeAttack] = useState(true)
|
||||
const [fullAuto, setFullAuto] = useState(false)
|
||||
const [autoGuard, setAutoGuard] = useState(false)
|
||||
|
||||
const [buttonCount, setButtonCount] = useState<number | undefined>(undefined)
|
||||
const [chainCount, setChainCount] = useState<number | undefined>(undefined)
|
||||
const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
|
||||
const [clearTime, setClearTime] = useState(0)
|
||||
|
||||
const [raidSlug, setRaidSlug] = useState('')
|
||||
const [embeddedDescription, setEmbeddedDescription] =
|
||||
useState<React.ReactNode>()
|
||||
|
|
@ -88,23 +105,18 @@ const PartyDetails = (props: Props) => {
|
|||
description: '',
|
||||
})
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
useEffect(() => {
|
||||
if (props.party) {
|
||||
setName(props.party.name)
|
||||
setAutoGuard(props.party.auto_guard)
|
||||
setFullAuto(props.party.full_auto)
|
||||
setChargeAttack(props.party.charge_attack)
|
||||
setClearTime(props.party.clear_time)
|
||||
if (props.party.turn_count) setTurnCount(props.party.turn_count)
|
||||
if (props.party.button_count) setButtonCount(props.party.button_count)
|
||||
if (props.party.chain_count) setChainCount(props.party.chain_count)
|
||||
}
|
||||
}, [props.party])
|
||||
|
||||
useEffect(() => {
|
||||
// Extract the video IDs from the description
|
||||
|
|
@ -139,6 +151,100 @@ const PartyDetails = (props: Props) => {
|
|||
}
|
||||
}, [appState.party.description])
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
setName(value)
|
||||
|
||||
let newErrors = errors
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
function handleChargeAttackChanged(checked: boolean) {
|
||||
setChargeAttack(checked)
|
||||
}
|
||||
|
||||
function handleFullAutoChanged(checked: boolean) {
|
||||
setFullAuto(checked)
|
||||
}
|
||||
|
||||
function handleAutoGuardChanged(checked: boolean) {
|
||||
setAutoGuard(checked)
|
||||
}
|
||||
|
||||
function handleClearTimeInput(value: number) {
|
||||
if (!isNaN(value)) setClearTime(value)
|
||||
}
|
||||
|
||||
function handleTurnCountInput(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseInt(event.currentTarget.value)
|
||||
if (!isNaN(value)) setTurnCount(value)
|
||||
}
|
||||
|
||||
function handleButtonCountInput(event: ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseInt(event.currentTarget.value)
|
||||
if (!isNaN(value)) setButtonCount(value)
|
||||
}
|
||||
|
||||
function handleChainCountInput(event: ChangeEvent<HTMLInputElement>) {
|
||||
const value = parseInt(event.currentTarget.value)
|
||||
if (!isNaN(value)) setChainCount(value)
|
||||
}
|
||||
|
||||
function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||
if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) {
|
||||
// Allow the key to be processed normally
|
||||
return
|
||||
}
|
||||
|
||||
// Get the current value
|
||||
const input = event.currentTarget
|
||||
let value = event.currentTarget.value
|
||||
|
||||
// Check if the key that was pressed is the backspace key
|
||||
if (event.key === 'Backspace') {
|
||||
// Remove the colon if the value is "12:"
|
||||
if (value.length === 4) {
|
||||
value = value.slice(0, -1)
|
||||
}
|
||||
|
||||
// Allow the backspace key to be processed normally
|
||||
input.value = value
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the key that was pressed is the tab key
|
||||
if (event.key === 'Tab') {
|
||||
// Allow the tab key to be processed normally
|
||||
return
|
||||
}
|
||||
|
||||
// Get the character that was entered and check if it is numeric
|
||||
const char = parseInt(event.key)
|
||||
const isNumber = !isNaN(char)
|
||||
|
||||
// Check if the character should be accepted or rejected
|
||||
const numberValue = parseInt(`${value}${char}`)
|
||||
const minValue = parseInt(event.currentTarget.min)
|
||||
const maxValue = parseInt(event.currentTarget.max)
|
||||
|
||||
if (!isNumber || numberValue < minValue || numberValue > maxValue) {
|
||||
// Reject the character if it isn't a number,
|
||||
// or if it exceeds the min and max values
|
||||
event.preventDefault()
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchYoutubeData(videoId: string) {
|
||||
return await youtube
|
||||
.getVideoById(videoId, { maxResults: 1 })
|
||||
|
|
@ -146,6 +252,11 @@ const PartyDetails = (props: Props) => {
|
|||
}
|
||||
|
||||
function toggleDetails() {
|
||||
if (name !== party.name) {
|
||||
const resetName = party.name ? party.name : 'Untitled'
|
||||
setName(resetName)
|
||||
if (nameInput.current) nameInput.current.value = resetName
|
||||
}
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
|
|
@ -153,12 +264,30 @@ const PartyDetails = (props: Props) => {
|
|||
if (slug) setRaidSlug(slug)
|
||||
}
|
||||
|
||||
function switchValue(value: boolean) {
|
||||
if (value) return 'on'
|
||||
else return 'off'
|
||||
}
|
||||
|
||||
function updateDetails(event: React.MouseEvent) {
|
||||
const nameValue = nameInput.current?.value
|
||||
const descriptionValue = descriptionInput.current?.value
|
||||
const raid = raids.find((raid) => raid.slug === raidSlug)
|
||||
|
||||
props.updateCallback(nameValue, descriptionValue, raid)
|
||||
const details: DetailsObject = {
|
||||
fullAuto: fullAuto,
|
||||
autoGuard: autoGuard,
|
||||
chargeAttack: chargeAttack,
|
||||
clearTime: clearTime,
|
||||
buttonCount: buttonCount,
|
||||
turnCount: turnCount,
|
||||
chainCount: chainCount,
|
||||
name: nameValue,
|
||||
description: descriptionValue,
|
||||
raid: raid,
|
||||
}
|
||||
|
||||
props.updateCallback(details)
|
||||
toggleDetails()
|
||||
}
|
||||
|
||||
|
|
@ -305,10 +434,114 @@ const PartyDetails = (props: Props) => {
|
|||
currentRaid={props.party?.raid ? props.party?.raid.slug : undefined}
|
||||
onChange={receiveRaid}
|
||||
/>
|
||||
<ul className="SwitchToggleGroup DetailToggleGroup">
|
||||
<li className="Ougi ToggleSection">
|
||||
<label htmlFor="ougi">
|
||||
<span>{t('party.details.labels.charge_attack')}</span>
|
||||
<div>
|
||||
<Switch
|
||||
name="charge_attack"
|
||||
onCheckedChange={handleChargeAttackChanged}
|
||||
value={switchValue(chargeAttack)}
|
||||
checked={chargeAttack}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
<li className="FullAuto ToggleSection">
|
||||
<label htmlFor="full_auto">
|
||||
<span>{t('party.details.labels.full_auto')}</span>
|
||||
<div>
|
||||
<Switch
|
||||
onCheckedChange={handleFullAutoChanged}
|
||||
name="full_auto"
|
||||
value={switchValue(fullAuto)}
|
||||
checked={fullAuto}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
<li className="AutoGuard ToggleSection">
|
||||
<label htmlFor="auto_guard">
|
||||
<span>{t('party.details.labels.auto_guard')}</span>
|
||||
<div>
|
||||
<Switch
|
||||
onCheckedChange={handleAutoGuardChanged}
|
||||
name="auto_guard"
|
||||
value={switchValue(autoGuard)}
|
||||
disabled={!fullAuto}
|
||||
checked={autoGuard}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="InputToggleGroup DetailToggleGroup">
|
||||
<li className="InputSection">
|
||||
<label htmlFor="auto_guard">
|
||||
<span>{t('party.details.labels.button_chain')}</span>
|
||||
<div className="Input Bound">
|
||||
<Input
|
||||
name="buttons"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
value={`${buttonCount}`}
|
||||
min="0"
|
||||
max="99"
|
||||
onChange={handleButtonCountInput}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<span>b</span>
|
||||
<Input
|
||||
name="chains"
|
||||
type="number"
|
||||
placeholder="0"
|
||||
min="0"
|
||||
max="99"
|
||||
value={`${chainCount}`}
|
||||
onChange={handleChainCountInput}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<span>c</span>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
<li className="InputSection">
|
||||
<label htmlFor="auto_guard">
|
||||
<span>{t('party.details.labels.turn_count')}</span>
|
||||
<Input
|
||||
name="turn_count"
|
||||
className="AlignRight Bound"
|
||||
type="number"
|
||||
step="1"
|
||||
min="1"
|
||||
max="999"
|
||||
placeholder="0"
|
||||
value={`${turnCount}`}
|
||||
onChange={handleTurnCountInput}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
</label>
|
||||
</li>
|
||||
<li className="InputSection">
|
||||
<label htmlFor="auto_guard">
|
||||
<span>{t('party.details.labels.clear_time')}</span>
|
||||
<div>
|
||||
<DurationInput
|
||||
name="clear_time"
|
||||
className="Bound"
|
||||
placeholder="00:00"
|
||||
value={clearTime}
|
||||
onValueChange={(value: number) => handleClearTimeInput(value)}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<TextFieldset
|
||||
fieldName="name"
|
||||
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!'
|
||||
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 3 first\nGood luck with RNG!'
|
||||
}
|
||||
value={props.party?.description}
|
||||
onChange={handleTextAreaChange}
|
||||
|
|
@ -332,8 +565,63 @@ const PartyDetails = (props: Props) => {
|
|||
</section>
|
||||
)
|
||||
|
||||
const clearTimeString = () => {
|
||||
const minutes = Math.floor(clearTime / 60)
|
||||
const seconds = clearTime - minutes * 60
|
||||
|
||||
if (minutes > 0)
|
||||
return `${minutes}${t('party.details.suffix.minutes')} ${seconds}${t(
|
||||
'party.details.suffix.seconds'
|
||||
)}`
|
||||
else return `${seconds}${t('party.details.suffix.seconds')}`
|
||||
}
|
||||
|
||||
const buttonChainToken = () => {
|
||||
if (buttonCount || chainCount) {
|
||||
let string = ''
|
||||
|
||||
if (buttonCount && buttonCount > 0) {
|
||||
string += `${buttonCount}b`
|
||||
}
|
||||
|
||||
if (!buttonCount && chainCount && chainCount > 0) {
|
||||
string += `0${t('party.details.suffix.buttons')}${chainCount}${t(
|
||||
'party.details.suffix.chains'
|
||||
)}`
|
||||
} else if (buttonCount && chainCount && chainCount > 0) {
|
||||
string += `${chainCount}${t('party.details.suffix.chains')}`
|
||||
} else if (buttonCount && !chainCount) {
|
||||
string += `0${t('party.details.suffix.chains')}`
|
||||
}
|
||||
|
||||
return <Token>{string}</Token>
|
||||
}
|
||||
}
|
||||
|
||||
const readOnly = (
|
||||
<section className={readOnlyClasses}>
|
||||
<section className="Details">
|
||||
{
|
||||
<Token>
|
||||
{`${t('party.details.labels.charge_attack')} ${
|
||||
chargeAttack ? 'On' : 'Off'
|
||||
}`}
|
||||
</Token>
|
||||
}
|
||||
{fullAuto ? <Token>{t('party.details.labels.full_auto')}</Token> : ''}
|
||||
{autoGuard ? <Token>{t('party.details.labels.auto_guard')}</Token> : ''}
|
||||
{turnCount ? (
|
||||
<Token>
|
||||
{t('party.details.turns.with_count', {
|
||||
count: turnCount,
|
||||
})}
|
||||
</Token>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{clearTime > 0 ? <Token>{clearTimeString()}</Token> : ''}
|
||||
{buttonChainToken()}
|
||||
</section>
|
||||
<Linkify>{embeddedDescription}</Linkify>
|
||||
</section>
|
||||
)
|
||||
|
|
@ -342,8 +630,8 @@ const PartyDetails = (props: Props) => {
|
|||
<section className="DetailsWrapper">
|
||||
<div className="PartyInfo">
|
||||
<div className="Left">
|
||||
<h1 className={!party.name ? 'empty' : ''}>
|
||||
{party.name ? party.name : 'Untitled'}
|
||||
<h1 className={name === '' ? 'empty' : ''}>
|
||||
{name !== '' ? name : 'Untitled'}
|
||||
</h1>
|
||||
<div className="attribution">
|
||||
{renderUserBlock()}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
DialogClose,
|
||||
} from '~components/Dialog'
|
||||
|
||||
import Input from '~components/Input'
|
||||
import Input from '~components/LabelledInput'
|
||||
import CharacterSearchFilterBar from '~components/CharacterSearchFilterBar'
|
||||
import WeaponSearchFilterBar from '~components/WeaponSearchFilterBar'
|
||||
import SummonSearchFilterBar from '~components/SummonSearchFilterBar'
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import setUserToken from '~utils/setUserToken'
|
|||
import { accountState } from '~utils/accountState'
|
||||
|
||||
import Button from '~components/Button'
|
||||
import Input from '~components/Input'
|
||||
import Input from '~components/LabelledInput'
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
|
|
|
|||
49
components/Switch/index.scss
Normal file
49
components/Switch/index.scss
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
.Switch {
|
||||
$height: 34px;
|
||||
background: $grey-70;
|
||||
border-radius: calc($height / 2);
|
||||
border: none;
|
||||
position: relative;
|
||||
width: 58px;
|
||||
height: $height;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px $grey-15;
|
||||
}
|
||||
|
||||
&[data-state='checked'] {
|
||||
background: $grey-15;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
box-shadow: none;
|
||||
|
||||
&:hover,
|
||||
.SwitchThumb:hover {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.SwitchThumb {
|
||||
background: $grey-80;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.SwitchThumb {
|
||||
background: $grey-100;
|
||||
border-radius: 13px;
|
||||
display: block;
|
||||
height: 26px;
|
||||
width: 26px;
|
||||
transition: transform 100ms;
|
||||
transform: translateX(-1px);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&[data-state='checked'] {
|
||||
background: $grey-100;
|
||||
transform: translateX(21px);
|
||||
}
|
||||
}
|
||||
46
components/Switch/index.tsx
Normal file
46
components/Switch/index.tsx
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from 'react'
|
||||
import * as RadixSwitch from '@radix-ui/react-switch'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
> {
|
||||
thumbClass?: string
|
||||
onCheckedChange: (checked: boolean) => void
|
||||
}
|
||||
|
||||
const Switch = (props: Props) => {
|
||||
const {
|
||||
checked,
|
||||
className,
|
||||
disabled,
|
||||
name,
|
||||
onCheckedChange,
|
||||
required,
|
||||
thumbClass,
|
||||
value,
|
||||
} = props
|
||||
|
||||
const mainClasses = classNames({ Switch: true }, className)
|
||||
const thumbClasses = classNames({ SwitchThumb: true }, thumbClass)
|
||||
|
||||
return (
|
||||
<RadixSwitch.Root
|
||||
className={mainClasses}
|
||||
checked={checked}
|
||||
name={name}
|
||||
disabled={disabled}
|
||||
required={required}
|
||||
value={value}
|
||||
onCheckedChange={onCheckedChange}
|
||||
>
|
||||
<RadixSwitch.Thumb className={thumbClasses} />
|
||||
</RadixSwitch.Root>
|
||||
)
|
||||
}
|
||||
|
||||
export default Switch
|
||||
10
components/Token/index.scss
Normal file
10
components/Token/index.scss
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
.Token {
|
||||
background: var(--input-bg);
|
||||
border-radius: 99px;
|
||||
display: inline;
|
||||
font-size: $font-tiny;
|
||||
font-weight: $bold;
|
||||
min-width: 3rem;
|
||||
text-align: center;
|
||||
padding: $unit-half $unit;
|
||||
}
|
||||
20
components/Token/index.tsx
Normal file
20
components/Token/index.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import classNames from 'classnames'
|
||||
import React from 'react'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {}
|
||||
|
||||
const Token = React.forwardRef<HTMLDivElement, Props>(function Token(
|
||||
{ children, className, ...props },
|
||||
forwardedRef
|
||||
) {
|
||||
const classes = classNames({ Token: true }, className)
|
||||
return <div className={classes}>{children}</div>
|
||||
})
|
||||
|
||||
export default Token
|
||||
27
package-lock.json
generated
27
package-lock.json
generated
|
|
@ -29,6 +29,7 @@
|
|||
"next-i18next": "^10.5.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"next-usequerystate": "^1.7.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.15.5",
|
||||
|
|
@ -46,6 +47,7 @@
|
|||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/node": "17.0.11",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/react": "17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-infinite-scroller": "^1.2.2",
|
||||
|
|
@ -2938,6 +2940,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||
},
|
||||
"node_modules/@types/pluralize": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz",
|
||||
"integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
|
|
@ -5965,6 +5973,14 @@
|
|||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.2.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz",
|
||||
|
|
@ -9201,6 +9217,12 @@
|
|||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA=="
|
||||
},
|
||||
"@types/pluralize": {
|
||||
"version": "0.0.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz",
|
||||
"integrity": "sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
|
|
@ -11398,6 +11420,11 @@
|
|||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
|
||||
},
|
||||
"pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.2.15",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.15.tgz",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"next-i18next": "^10.5.0",
|
||||
"next-themes": "^0.2.1",
|
||||
"next-usequerystate": "^1.7.0",
|
||||
"pluralize": "^8.0.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.15.5",
|
||||
|
|
@ -51,6 +52,7 @@
|
|||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/lodash.debounce": "^4.0.6",
|
||||
"@types/node": "17.0.11",
|
||||
"@types/pluralize": "^0.0.29",
|
||||
"@types/react": "17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/react-infinite-scroller": "^1.2.2",
|
||||
|
|
|
|||
|
|
@ -258,6 +258,26 @@
|
|||
"characters": "Characters",
|
||||
"weapons": "Weapons",
|
||||
"summons": "Summons"
|
||||
},
|
||||
"details": {
|
||||
"labels": {
|
||||
"charge_attack": "Charge Attack",
|
||||
"full_auto": "Full Auto",
|
||||
"auto_guard": "Auto Guard",
|
||||
"turn_count": "Turn count",
|
||||
"button_chain": "Buttons/Chains",
|
||||
"clear_time": "Clear time"
|
||||
},
|
||||
"suffix": {
|
||||
"buttons": "b",
|
||||
"chains": "c",
|
||||
"minutes": "m",
|
||||
"seconds": "s"
|
||||
},
|
||||
"turns": {
|
||||
"with_count_one": "{{count}} turn",
|
||||
"with_count_other": "{{count}} turns"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": {
|
||||
|
|
|
|||
|
|
@ -259,6 +259,26 @@
|
|||
"characters": "キャラ",
|
||||
"weapons": "武器",
|
||||
"summons": "召喚石"
|
||||
},
|
||||
"details": {
|
||||
"labels": {
|
||||
"charge_attack": "奥義",
|
||||
"full_auto": "フルオート",
|
||||
"auto_guard": "オートガード",
|
||||
"turn_count": "経過ターン",
|
||||
"button_chain": "ポチチェイン",
|
||||
"clear_time": "討伐時間"
|
||||
},
|
||||
"suffix": {
|
||||
"buttons": "ポチ",
|
||||
"chains": "チェ",
|
||||
"minutes": "分",
|
||||
"seconds": "秒"
|
||||
},
|
||||
"turns": {
|
||||
"with_count_one": "{{count}}ターン",
|
||||
"with_count_other": "{{count}}ターン"
|
||||
}
|
||||
}
|
||||
},
|
||||
"saved": {
|
||||
|
|
|
|||
7
types/Party.d.ts
vendored
7
types/Party.d.ts
vendored
|
|
@ -11,6 +11,13 @@ interface Party {
|
|||
name: string
|
||||
description: string
|
||||
raid: Raid
|
||||
full_auto: boolean
|
||||
auto_guard: boolean
|
||||
charge_attack: boolean
|
||||
clear_time: number
|
||||
button_count?: number
|
||||
turn_count?: number
|
||||
chain_count?: number
|
||||
job: Job
|
||||
job_skills: JobSkillObject
|
||||
shortcode: string
|
||||
|
|
|
|||
14
types/index.d.ts
vendored
14
types/index.d.ts
vendored
|
|
@ -19,3 +19,17 @@ export type PaginationObject = {
|
|||
totalPages: number
|
||||
perPage: number
|
||||
}
|
||||
|
||||
export type DetailsObject = {
|
||||
[key: string]: boolean | number | string | Raid | undefined
|
||||
fullAuto: boolean
|
||||
autoGuard: boolean
|
||||
chargeAttack: boolean
|
||||
clearTime: number
|
||||
buttonCount?: number
|
||||
turnCount?: number
|
||||
chainCount?: number
|
||||
name?: string
|
||||
description?: string
|
||||
raid?: Raid
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ interface AppState {
|
|||
jobSkills: JobSkillObject
|
||||
raid: Raid | undefined
|
||||
element: number
|
||||
fullAuto: boolean
|
||||
autoGuard: boolean
|
||||
chargeAttack: boolean
|
||||
clearTime: number
|
||||
buttonCount?: number
|
||||
turnCount?: number
|
||||
chainCount?: number
|
||||
extra: boolean
|
||||
user: User | undefined
|
||||
favorited: boolean
|
||||
|
|
@ -76,6 +83,13 @@ export const initialAppState: AppState = {
|
|||
3: undefined,
|
||||
},
|
||||
raid: undefined,
|
||||
fullAuto: false,
|
||||
autoGuard: false,
|
||||
chargeAttack: true,
|
||||
clearTime: 0,
|
||||
buttonCount: undefined,
|
||||
turnCount: undefined,
|
||||
chainCount: undefined,
|
||||
element: 0,
|
||||
extra: false,
|
||||
user: undefined,
|
||||
|
|
|
|||
Loading…
Reference in a new issue