Implement Edit team modal (#312)
* Small refactor to CharLimitedFieldset Some methods were renamed for clarity. <input> props are actually put on the input properly. * Add tabindex to Popover trigger * Add tabindex to Switch and SwitchTableField * Add tabindex to DurationInput * Add new properties * Added guidebooks to RaidGroup * Added auto_summon to Party * Conditionally render description in TableField * Improve SwitchTableField * Add support for passing in classes * Add support for passing a disabled prop * Pass description to TableField * Right-align switch * Add support for Extra color switch * Align SliderTableField input to right * Align SelectTableField input to right * Update placeholder styles * Fix empty state on DurationInput * Remove tabindex from DurationInput * Update InputTableField Allow for passing down input properties and remove fixed width * Fix dialog footer styles * Update dialog and overlay z-index * Add styles to TableField Added styles for numeric inputs, disabled inputs, and generally cleaning things up * Add guidebooks to RaidCombobox + styles * Added guidebooks to the dummy raid group * Fix background color * Make less tall * Implement EditPartyModal EditPartyModal takes functionality that was in PartyHeader and puts it in a modal dialog. This lets us add fields and reduces the complexity of other components. Translations were also added. * Remove edit functionality * Add darker shadow to Select * Properly send raid ID to server * Show Extra grids based on selected raid * Fix EX badge colors * Use child as value in normal textarea * Remove toggle ability from Extra grids * Remove edit functionality from PartyDetails
This commit is contained in:
parent
938e34f21c
commit
835cdfff6f
35 changed files with 853 additions and 581 deletions
|
|
@ -414,7 +414,8 @@ const FilterModal = (props: Props) => {
|
|||
{originalOnlyField()}
|
||||
</div>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<div className="Buttons Spaced">
|
||||
<div className="Left"></div>
|
||||
<div className="Right Buttons Spaced">
|
||||
<Button
|
||||
blended={true}
|
||||
text={t('modals.filters.buttons.clear')}
|
||||
|
|
|
|||
|
|
@ -330,10 +330,13 @@ const AccountModal = React.forwardRef<HTMLDivElement, Props>(
|
|||
{themeField()}
|
||||
</div>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<Button
|
||||
contained={true}
|
||||
text={t('modals.settings.buttons.confirm')}
|
||||
/>
|
||||
<div className="Left"></div>
|
||||
<div className="Right">
|
||||
<Button
|
||||
contained={true}
|
||||
text={t('modals.settings.buttons.confirm')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import React, {
|
||||
ForwardRefRenderFunction,
|
||||
forwardRef,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
import classNames from 'classnames'
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
interface Props extends React.HTMLProps<HTMLInputElement> {
|
||||
fieldName: string
|
||||
placeholder: string
|
||||
value?: string
|
||||
|
|
@ -11,47 +18,61 @@ interface Props {
|
|||
onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
}
|
||||
|
||||
const CharLimitedFieldset = React.forwardRef<HTMLInputElement, Props>(
|
||||
function useFieldSet(props, ref) {
|
||||
const fieldType = ['password', 'confirm_password'].includes(props.fieldName)
|
||||
? 'password'
|
||||
: 'text'
|
||||
const CharLimitedFieldset: ForwardRefRenderFunction<HTMLInputElement, Props> = (
|
||||
{
|
||||
fieldName,
|
||||
placeholder,
|
||||
value,
|
||||
limit,
|
||||
error,
|
||||
onBlur,
|
||||
onChange: onInputChange,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
// States
|
||||
const [currentCount, setCurrentCount] = useState(
|
||||
() => limit - (value || '').length
|
||||
)
|
||||
|
||||
const [currentCount, setCurrentCount] = useState(0)
|
||||
// Hooks
|
||||
useEffect(() => {
|
||||
setCurrentCount(limit - (value || '').length)
|
||||
}, [limit, value])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentCount(
|
||||
props.value ? props.limit - props.value.length : props.limit
|
||||
)
|
||||
}, [props.limit, props.value])
|
||||
|
||||
function onChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setCurrentCount(props.limit - event.currentTarget.value.length)
|
||||
if (props.onChange) props.onChange(event)
|
||||
// Event handlers
|
||||
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { value: inputValue } = event.currentTarget
|
||||
setCurrentCount(limit - inputValue.length)
|
||||
if (onInputChange) {
|
||||
onInputChange(event)
|
||||
}
|
||||
|
||||
return (
|
||||
<fieldset className="Fieldset">
|
||||
<div className="Joined">
|
||||
<input
|
||||
autoComplete="off"
|
||||
className="Input"
|
||||
type={fieldType}
|
||||
name={props.fieldName}
|
||||
placeholder={props.placeholder}
|
||||
defaultValue={props.value || ''}
|
||||
onBlur={props.onBlur}
|
||||
onChange={onChange}
|
||||
maxLength={props.limit}
|
||||
ref={ref}
|
||||
formNoValidate
|
||||
/>
|
||||
<span className="Counter">{currentCount}</span>
|
||||
</div>
|
||||
{props.error.length > 0 && <p className="InputError">{props.error}</p>}
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
export default CharLimitedFieldset
|
||||
// Rendering methods
|
||||
return (
|
||||
<fieldset className="Fieldset">
|
||||
<div className={classNames({ Joined: true }, props.className)}>
|
||||
<input
|
||||
{...props}
|
||||
autoComplete="off"
|
||||
className="Input"
|
||||
type={props.type}
|
||||
name={fieldName}
|
||||
placeholder={placeholder}
|
||||
defaultValue={value || ''}
|
||||
onBlur={onBlur}
|
||||
onChange={handleInputChange}
|
||||
maxLength={limit}
|
||||
ref={ref}
|
||||
formNoValidate
|
||||
/>
|
||||
<span className="Counter">{currentCount}</span>
|
||||
</div>
|
||||
{error.length > 0 && <p className="InputError">{error}</p>}
|
||||
</fieldset>
|
||||
)
|
||||
}
|
||||
|
||||
export default forwardRef(CharLimitedFieldset)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
min-width: 100vw;
|
||||
overflow-y: auto;
|
||||
color: inherit;
|
||||
z-index: 40;
|
||||
z-index: 10;
|
||||
|
||||
.DialogContent {
|
||||
$multiplier: 4;
|
||||
|
|
@ -160,7 +160,8 @@
|
|||
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.16);
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.24);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: ($unit * 1.5) ($unit * $multiplier) $unit-3x;
|
||||
position: sticky;
|
||||
|
||||
|
|
@ -178,7 +179,6 @@
|
|||
|
||||
&.Spaced {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ interface Props
|
|||
}
|
||||
|
||||
const DurationInput = React.forwardRef<HTMLInputElement, Props>(
|
||||
function DurationInput({ className, value, onValueChange }, forwardedRef) {
|
||||
function DurationInput(
|
||||
{ className, value, onValueChange, ...props },
|
||||
forwardedRef
|
||||
) {
|
||||
// State
|
||||
const [duration, setDuration] = useState('')
|
||||
const [minutesSelected, setMinutesSelected] = useState(false)
|
||||
|
|
@ -202,7 +205,7 @@ const DurationInput = React.forwardRef<HTMLInputElement, Props>(
|
|||
},
|
||||
className
|
||||
)}
|
||||
value={`${getSeconds()}`.padStart(2, '0')}
|
||||
value={getSeconds() > 0 ? `${getSeconds()}`.padStart(2, '0') : ''}
|
||||
onChange={handleSecondsChange}
|
||||
onKeyUp={handleKeyUp}
|
||||
onKeyDown={handleKeyDown}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@
|
|||
&:hover {
|
||||
background-color: var(--input-bound-bg-hover);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: var(--text-tertiary) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.AlignRight {
|
||||
|
|
@ -43,7 +48,7 @@
|
|||
width: 0;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
.Input::placeholder {
|
||||
/* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: var(--text-secondary) !important;
|
||||
opacity: 1; /* Firefox */
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
.InputField.TableField .Input {
|
||||
.InputField.TableField .Input[type='number'] {
|
||||
text-align: right;
|
||||
width: $unit-8x;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,50 +3,60 @@ import Input from '~components/common/Input'
|
|||
import TableField from '~components/common/TableField'
|
||||
|
||||
import './index.scss'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
name: string
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
HTMLInputElement
|
||||
> {
|
||||
label: string
|
||||
description?: string
|
||||
placeholder?: string
|
||||
value?: number
|
||||
className?: string
|
||||
imageAlt?: string
|
||||
imageClass?: string
|
||||
imageSrc?: string[]
|
||||
onValueChange: (value: number) => void
|
||||
onValueChange: (value?: string) => void
|
||||
}
|
||||
|
||||
const InputTableField = (props: Props) => {
|
||||
const [value, setValue] = useState(0)
|
||||
const InputTableField = ({
|
||||
label,
|
||||
description,
|
||||
imageAlt,
|
||||
imageClass,
|
||||
imageSrc,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [inputValue, setInputValue] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
if (props.value) setValue(props.value)
|
||||
if (props.value) setInputValue(`${props.value}`)
|
||||
}, [props.value])
|
||||
|
||||
useEffect(() => {
|
||||
props.onValueChange(value)
|
||||
}, [value])
|
||||
props.onValueChange(inputValue)
|
||||
}, [inputValue])
|
||||
|
||||
function onInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
setValue(parseInt(event.currentTarget?.value))
|
||||
setInputValue(`${parseInt(event.currentTarget?.value)}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<TableField
|
||||
name={props.name}
|
||||
className="InputField"
|
||||
imageAlt={props.imageAlt}
|
||||
imageClass={props.imageClass}
|
||||
imageSrc={props.imageSrc}
|
||||
label={props.label}
|
||||
{...props}
|
||||
name={props.name || ''}
|
||||
className={classNames({ InputField: true }, props.className)}
|
||||
imageAlt={imageAlt}
|
||||
imageClass={imageClass}
|
||||
imageSrc={imageSrc}
|
||||
label={label}
|
||||
>
|
||||
<Input
|
||||
className="Bound"
|
||||
placeholder={props.placeholder}
|
||||
type="number"
|
||||
value={value ? `${value}` : ''}
|
||||
value={inputValue ? `${inputValue}` : ''}
|
||||
step={1}
|
||||
tabIndex={props.tabIndex}
|
||||
type={props.type}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</TableField>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
.Overlay {
|
||||
isolation: isolate;
|
||||
position: fixed;
|
||||
z-index: 30;
|
||||
z-index: 9;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ interface Props extends ComponentProps<'div'> {
|
|||
className?: string
|
||||
placeholder?: string
|
||||
}
|
||||
triggerTabIndex?: number
|
||||
value?: {
|
||||
element: ReactNode
|
||||
rawValue: string
|
||||
|
|
@ -83,6 +84,7 @@ const Popover = React.forwardRef<HTMLDivElement, Props>(function Popover(
|
|||
<PopoverPrimitive.Trigger
|
||||
className={triggerClasses}
|
||||
data-placeholder={!props.value}
|
||||
tabIndex={props.triggerTabIndex}
|
||||
>
|
||||
{icon}
|
||||
{value}
|
||||
|
|
|
|||
|
|
@ -75,8 +75,8 @@
|
|||
.Select {
|
||||
background: var(--dialog-bg);
|
||||
border-radius: $card-corner;
|
||||
border: $hover-stroke;
|
||||
box-shadow: $hover-shadow;
|
||||
border: 1px solid rgba(0, 0, 0, 0.24);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16);
|
||||
padding: 0 $unit;
|
||||
z-index: 40;
|
||||
min-width: var(--radix-select-trigger-width);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
.SelectField.TableField .Right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
|
@ -31,6 +31,7 @@ const SelectTableField = (props: Props) => {
|
|||
return (
|
||||
<TableField
|
||||
name={props.name}
|
||||
className="SelectField"
|
||||
imageAlt={props.imageAlt}
|
||||
imageClass={props.imageClass}
|
||||
imageSrc={props.imageSrc}
|
||||
|
|
|
|||
|
|
@ -5,4 +5,8 @@
|
|||
text-align: right;
|
||||
width: $unit-8x;
|
||||
}
|
||||
|
||||
.Right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const Switch = (props: Props) => {
|
|||
disabled={disabled}
|
||||
required={required}
|
||||
value={value}
|
||||
tabIndex={props.tabIndex}
|
||||
onCheckedChange={onCheckedChange}
|
||||
>
|
||||
<RadixSwitch.Thumb className={thumbClasses} />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
.TableField.SwitchTableField {
|
||||
&.Extra .Switch[data-state='checked'] {
|
||||
background: var(--extra-purple-secondary);
|
||||
}
|
||||
|
||||
.Right {
|
||||
justify-content: end;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,18 @@
|
|||
import { useEffect, useState } from 'react'
|
||||
import classNames from 'classnames'
|
||||
import Switch from '~components/common/Switch'
|
||||
import TableField from '~components/common/TableField'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
name: string
|
||||
label: string
|
||||
description?: string
|
||||
disabled?: boolean
|
||||
value?: boolean
|
||||
className?: string
|
||||
tabIndex?: number
|
||||
imageAlt?: string
|
||||
imageClass?: string
|
||||
imageSrc?: string[]
|
||||
|
|
@ -31,10 +34,19 @@ const SwitchTableField = (props: Props) => {
|
|||
setValue(value)
|
||||
}
|
||||
|
||||
const classes = classNames(
|
||||
{
|
||||
SwitchTableField: true,
|
||||
Disabled: props.disabled,
|
||||
},
|
||||
props.className
|
||||
)
|
||||
|
||||
return (
|
||||
<TableField
|
||||
name={props.name}
|
||||
className="SwitchField"
|
||||
description={props.description}
|
||||
className={classes}
|
||||
imageAlt={props.imageAlt}
|
||||
imageClass={props.imageClass}
|
||||
imageSrc={props.imageSrc}
|
||||
|
|
@ -43,6 +55,8 @@ const SwitchTableField = (props: Props) => {
|
|||
<Switch
|
||||
name={props.name}
|
||||
checked={value}
|
||||
disabled={props.disabled}
|
||||
tabIndex={props.tabIndex}
|
||||
onCheckedChange={onValueChange}
|
||||
/>
|
||||
</TableField>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
display: grid;
|
||||
gap: $unit-2x;
|
||||
grid-template-columns: 1fr auto;
|
||||
min-height: $unit-6x;
|
||||
justify-content: space-between;
|
||||
padding: $unit-half 0;
|
||||
width: 100%;
|
||||
|
|
@ -17,7 +18,30 @@
|
|||
color: var(--accent-blue);
|
||||
}
|
||||
|
||||
&.Numeric .Right > .Input,
|
||||
&.Numeric .Right > .Duration {
|
||||
text-align: right;
|
||||
max-width: $unit-12x;
|
||||
width: $unit-12x;
|
||||
}
|
||||
|
||||
&.Numeric .Right > .Duration {
|
||||
justify-content: flex-end;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&.Disabled {
|
||||
&:hover .Left .Info h3 {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
.Left .Info h3 {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.Left {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
|
|
@ -59,7 +83,6 @@
|
|||
color: var(--text-secondary);
|
||||
font-size: $font-small;
|
||||
line-height: 1.1;
|
||||
max-width: 300px;
|
||||
|
||||
&.jp {
|
||||
max-width: 270px;
|
||||
|
|
@ -71,6 +94,7 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
gap: $unit-2x;
|
||||
width: 100%;
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ const TableField = (props: Props) => {
|
|||
<div className="Left">
|
||||
<div className="Info">
|
||||
<h3>{props.label}</h3>
|
||||
<p>{props.description}</p>
|
||||
{props.description && <p>{props.description}</p>}
|
||||
</div>
|
||||
<div className="Image">{image()}</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -11,12 +11,10 @@ import classNames from 'classnames'
|
|||
// Props
|
||||
interface Props {
|
||||
grid: GridArray<GridWeapon>
|
||||
enabled: boolean
|
||||
editable: boolean
|
||||
found?: boolean
|
||||
offset: number
|
||||
removeWeapon: (id: string) => void
|
||||
updateExtra: (enabled: boolean) => void
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
}
|
||||
|
|
@ -26,12 +24,9 @@ const EXTRA_WEAPONS_COUNT = 3
|
|||
|
||||
const ExtraWeaponsGrid = ({
|
||||
grid,
|
||||
enabled,
|
||||
editable,
|
||||
found,
|
||||
offset,
|
||||
removeWeapon,
|
||||
updateExtra,
|
||||
updateObject,
|
||||
updateUncap,
|
||||
}: Props) => {
|
||||
|
|
@ -40,16 +35,9 @@ const ExtraWeaponsGrid = ({
|
|||
const classes = classNames({
|
||||
ExtraWeapons: true,
|
||||
ContainerItem: true,
|
||||
Disabled: !enabled,
|
||||
})
|
||||
|
||||
function onCheckedChange(checked: boolean) {
|
||||
updateExtra(checked)
|
||||
}
|
||||
|
||||
const disabledElement = <></>
|
||||
|
||||
const enabledElement = (
|
||||
const extraWeapons = (
|
||||
<ul id="ExtraWeaponGrid">
|
||||
{Array.from(Array(EXTRA_WEAPONS_COUNT)).map((x, i) => {
|
||||
const itemClasses = classNames({
|
||||
|
|
@ -77,17 +65,8 @@ const ExtraWeaponsGrid = ({
|
|||
<div className={classes}>
|
||||
<div className="Header">
|
||||
<h3>{t('extra_weapons')}</h3>
|
||||
{editable ? (
|
||||
<Switch
|
||||
name="ExtraWeapons"
|
||||
checked={enabled}
|
||||
onCheckedChange={onCheckedChange}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
{enabled ? enabledElement : ''}
|
||||
{extraWeapons}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import './index.scss'
|
|||
interface Props {
|
||||
grid: GuidebookList
|
||||
editable: boolean
|
||||
offset: number
|
||||
removeGuidebook: (position: number) => void
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
}
|
||||
|
|
@ -28,28 +27,12 @@ const GuidebooksGrid = ({
|
|||
}: Props) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const [enabled, setEnabled] = useState(false)
|
||||
|
||||
const classes = classNames({
|
||||
Guidebooks: true,
|
||||
ContainerItem: true,
|
||||
Disabled: !enabled,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Grid updated')
|
||||
if (hasGuidebooks()) setEnabled(true)
|
||||
}, [grid])
|
||||
|
||||
function hasGuidebooks() {
|
||||
return grid && (grid[0] || grid[1] || grid[2])
|
||||
}
|
||||
|
||||
function onCheckedChange(checked: boolean) {
|
||||
setEnabled(checked)
|
||||
}
|
||||
|
||||
const enabledElement = (
|
||||
const guidebooks = (
|
||||
<ul id="GuidebooksGrid">
|
||||
{Array.from(Array(EXTRA_WEAPONS_COUNT)).map((x, i) => {
|
||||
const itemClasses = classNames({
|
||||
|
|
@ -75,21 +58,12 @@ const GuidebooksGrid = ({
|
|||
<div className={classes}>
|
||||
<div className="Header">
|
||||
<h3>{t('sephira_guidebooks')}</h3>
|
||||
{editable ? (
|
||||
<Switch
|
||||
name="Guidebooks"
|
||||
checked={enabled}
|
||||
onCheckedChange={onCheckedChange}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
{enabled ? enabledElement : ''}
|
||||
{guidebooks}
|
||||
</div>
|
||||
)
|
||||
|
||||
return editable || (enabled && !editable) ? guidebookElement : <div />
|
||||
return guidebookElement
|
||||
}
|
||||
|
||||
export default GuidebooksGrid
|
||||
|
|
|
|||
56
components/party/EditPartyModal/index.scss
Normal file
56
components/party/EditPartyModal/index.scss
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
.EditTeam.DialogContent {
|
||||
min-height: 80vh;
|
||||
|
||||
.Container.Scrollable {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.Content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
gap: $unit-2x;
|
||||
}
|
||||
|
||||
.Fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
gap: $unit;
|
||||
}
|
||||
|
||||
.ExtraNotice {
|
||||
background: var(--extra-purple-bg);
|
||||
border-radius: $input-corner;
|
||||
color: var(--extra-purple-text);
|
||||
font-weight: $medium;
|
||||
padding: $unit-2x;
|
||||
}
|
||||
|
||||
.DescriptionField {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: inherit;
|
||||
gap: $unit;
|
||||
flex-grow: 1;
|
||||
|
||||
.Left {
|
||||
flex-grow: 0;
|
||||
}
|
||||
|
||||
textarea.Input {
|
||||
flex-grow: 1;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.Image {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
477
components/party/EditPartyModal/index.tsx
Normal file
477
components/party/EditPartyModal/index.tsx
Normal file
|
|
@ -0,0 +1,477 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
import {
|
||||
Dialog,
|
||||
DialogTrigger,
|
||||
DialogClose,
|
||||
DialogTitle,
|
||||
} from '~components/common/Dialog'
|
||||
import DialogContent from '~components/common/DialogContent'
|
||||
import Button from '~components/common/Button'
|
||||
import CharLimitedFieldset from '~components/common/CharLimitedFieldset'
|
||||
import DurationInput from '~components/common/DurationInput'
|
||||
import InputTableField from '~components/common/InputTableField'
|
||||
import RaidCombobox from '~components/raids/RaidCombobox'
|
||||
import SegmentedControl from '~components/common/SegmentedControl'
|
||||
import Segment from '~components/common/Segment'
|
||||
import SwitchTableField from '~components/common/SwitchTableField'
|
||||
import TableField from '~components/common/TableField'
|
||||
|
||||
import type { DetailsObject } from 'types'
|
||||
import type { DialogProps } from '@radix-ui/react-dialog'
|
||||
|
||||
import CheckIcon from '~public/icons/Check.svg'
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import './index.scss'
|
||||
|
||||
interface Props extends DialogProps {
|
||||
party?: Party
|
||||
updateCallback: (details: DetailsObject) => void
|
||||
}
|
||||
|
||||
const EditPartyModal = ({ party, updateCallback, ...props }: Props) => {
|
||||
// Set up router
|
||||
const router = useRouter()
|
||||
const locale = router.locale
|
||||
|
||||
// Set up translation
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Refs
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
const descriptionInput = useRef<HTMLTextAreaElement>(null)
|
||||
|
||||
// States: Component
|
||||
const [open, setOpen] = useState(false)
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||||
name: '',
|
||||
description: '',
|
||||
})
|
||||
const [currentSegment, setCurrentSegment] = useState(0)
|
||||
|
||||
// States: Data
|
||||
const [name, setName] = useState('')
|
||||
const [raid, setRaid] = useState<Raid>()
|
||||
const [extra, setExtra] = useState(false)
|
||||
const [chargeAttack, setChargeAttack] = useState(true)
|
||||
const [fullAuto, setFullAuto] = useState(false)
|
||||
const [autoGuard, setAutoGuard] = useState(false)
|
||||
const [autoSummon, setAutoSummon] = 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)
|
||||
|
||||
// Hooks
|
||||
useEffect(() => {
|
||||
if (!party) return
|
||||
|
||||
setName(party.name)
|
||||
setRaid(party.raid)
|
||||
setAutoGuard(party.auto_guard)
|
||||
setAutoSummon(party.auto_summon)
|
||||
setFullAuto(party.full_auto)
|
||||
setChargeAttack(party.charge_attack)
|
||||
setClearTime(party.clear_time)
|
||||
if (party.turn_count) setTurnCount(party.turn_count)
|
||||
if (party.button_count) setButtonCount(party.button_count)
|
||||
if (party.chain_count) setChainCount(party.chain_count)
|
||||
}, [party])
|
||||
|
||||
// Methods: Event handlers (Dialog)
|
||||
function openChange() {
|
||||
if (open) {
|
||||
setOpen(false)
|
||||
if (props.onOpenChange) props.onOpenChange(false)
|
||||
} else {
|
||||
setOpen(true)
|
||||
if (props.onOpenChange) props.onOpenChange(true)
|
||||
}
|
||||
}
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
event.preventDefault()
|
||||
openChange()
|
||||
}
|
||||
|
||||
function onOpenAutoFocus(event: Event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// Methods: Event handlers (Fields)
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
setName(value)
|
||||
|
||||
let newErrors = errors
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
function handleChargeAttackChanged(checked: boolean) {
|
||||
setChargeAttack(checked)
|
||||
}
|
||||
|
||||
function handleFullAutoChanged(checked: boolean) {
|
||||
setFullAuto(checked)
|
||||
}
|
||||
|
||||
function handleAutoGuardChanged(checked: boolean) {
|
||||
setAutoGuard(checked)
|
||||
}
|
||||
|
||||
function handleAutoSummonChanged(checked: boolean) {
|
||||
setAutoSummon(checked)
|
||||
}
|
||||
|
||||
function handleExtraChanged(checked: boolean) {
|
||||
setExtra(checked)
|
||||
}
|
||||
|
||||
function handleClearTimeChanged(value: number) {
|
||||
if (!isNaN(value)) setClearTime(value)
|
||||
}
|
||||
|
||||
function handleTurnCountChanged(value?: string) {
|
||||
if (!value) return
|
||||
const numericalValue = parseInt(value)
|
||||
if (!isNaN(numericalValue)) setTurnCount(numericalValue)
|
||||
}
|
||||
|
||||
function handleButtonCountChanged(value?: string) {
|
||||
if (!value) return
|
||||
const numericalValue = parseInt(value)
|
||||
if (!isNaN(numericalValue)) setButtonCount(numericalValue)
|
||||
}
|
||||
|
||||
function handleChainCountChanged(value?: string) {
|
||||
if (!value) return
|
||||
const numericalValue = parseInt(value)
|
||||
if (!isNaN(numericalValue)) setChainCount(numericalValue)
|
||||
}
|
||||
|
||||
function handleTextAreaChanged(
|
||||
event: React.ChangeEvent<HTMLTextAreaElement>
|
||||
) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
function receiveRaid(raid?: Raid) {
|
||||
if (raid) {
|
||||
setRaid(raid)
|
||||
|
||||
if (raid.group.extra) setExtra(true)
|
||||
else setExtra(false)
|
||||
}
|
||||
}
|
||||
|
||||
// Methods: Data methods
|
||||
function updateDetails(event: React.MouseEvent) {
|
||||
const descriptionValue = descriptionInput.current?.value
|
||||
const details: DetailsObject = {
|
||||
fullAuto: fullAuto,
|
||||
autoGuard: autoGuard,
|
||||
autoSummon: autoSummon,
|
||||
chargeAttack: chargeAttack,
|
||||
clearTime: clearTime,
|
||||
buttonCount: buttonCount,
|
||||
turnCount: turnCount,
|
||||
chainCount: chainCount,
|
||||
name: name,
|
||||
description: descriptionValue,
|
||||
raid: raid,
|
||||
extra: extra,
|
||||
}
|
||||
|
||||
updateCallback(details)
|
||||
openChange()
|
||||
}
|
||||
|
||||
// Methods: Rendering methods
|
||||
const segmentedControl = () => {
|
||||
return (
|
||||
<SegmentedControl blended={true}>
|
||||
<Segment
|
||||
groupName="edit_nav"
|
||||
name="core"
|
||||
selected={currentSegment === 0}
|
||||
tabIndex={0}
|
||||
onClick={() => setCurrentSegment(0)}
|
||||
>
|
||||
{t('modals.edit_team.segments.basic_info')}
|
||||
</Segment>
|
||||
<Segment
|
||||
groupName="edit_nav"
|
||||
name="properties"
|
||||
selected={currentSegment === 1}
|
||||
tabIndex={0}
|
||||
onClick={() => setCurrentSegment(1)}
|
||||
>
|
||||
{t('modals.edit_team.segments.properties')}
|
||||
</Segment>
|
||||
</SegmentedControl>
|
||||
)
|
||||
}
|
||||
|
||||
const nameField = () => {
|
||||
return (
|
||||
<CharLimitedFieldset
|
||||
className="Bound"
|
||||
fieldName="name"
|
||||
placeholder="Name your team"
|
||||
value={name}
|
||||
limit={50}
|
||||
onChange={handleInputChange}
|
||||
error={errors.name}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const raidField = () => {
|
||||
return (
|
||||
<RaidCombobox
|
||||
showAllRaidsOption={false}
|
||||
currentRaid={raid}
|
||||
onChange={receiveRaid}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const extraNotice = () => {
|
||||
if (extra) {
|
||||
return (
|
||||
<div className="ExtraNotice">
|
||||
<span className="ExtraNoticeText">
|
||||
{raid && raid.group.guidebooks
|
||||
? t('modals.edit_team.extra_notice_guidebooks')
|
||||
: t('modals.edit_team.extra_notice')}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const descriptionField = () => {
|
||||
return (
|
||||
<div className="DescriptionField">
|
||||
<textarea
|
||||
className="Input Bound"
|
||||
name="description"
|
||||
placeholder={
|
||||
'Write your notes here\n\n\nWatch out for the 50% trigger!\nMake sure to click Fediel’s 3 first\nGood luck with RNG!'
|
||||
}
|
||||
onChange={handleTextAreaChanged}
|
||||
ref={descriptionInput}
|
||||
>
|
||||
{party ? party.description : ''}
|
||||
</textarea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const chargeAttackField = () => {
|
||||
return (
|
||||
<SwitchTableField
|
||||
name="charge_attack"
|
||||
label={t('modals.edit_team.labels.charge_attack')}
|
||||
value={chargeAttack}
|
||||
onValueChange={handleChargeAttackChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const fullAutoField = () => {
|
||||
return (
|
||||
<SwitchTableField
|
||||
name="full_auto"
|
||||
label={t('modals.edit_team.labels.full_auto')}
|
||||
value={fullAuto}
|
||||
onValueChange={handleFullAutoChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const autoGuardField = () => {
|
||||
return (
|
||||
<SwitchTableField
|
||||
name="auto_guard"
|
||||
label={t('modals.edit_team.labels.auto_guard')}
|
||||
value={autoGuard}
|
||||
onValueChange={handleAutoGuardChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const autoSummonField = () => {
|
||||
return (
|
||||
<SwitchTableField
|
||||
name="auto_summon"
|
||||
label={t('modals.edit_team.labels.auto_summon')}
|
||||
value={autoSummon}
|
||||
onValueChange={handleAutoSummonChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const extraField = () => {
|
||||
return (
|
||||
<SwitchTableField
|
||||
name="extra"
|
||||
className="Extra"
|
||||
label={t('modals.edit_team.labels.extra')}
|
||||
description={t('modals.edit_team.descriptions.extra')}
|
||||
value={extra}
|
||||
disabled={true}
|
||||
onValueChange={handleExtraChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const clearTimeField = () => {
|
||||
return (
|
||||
<TableField
|
||||
className="Numeric"
|
||||
name="clear_time"
|
||||
label={t('modals.edit_team.labels.clear_time')}
|
||||
>
|
||||
<DurationInput
|
||||
name="clear_time"
|
||||
className="Bound"
|
||||
value={clearTime}
|
||||
onValueChange={(value: number) => handleClearTimeChanged(value)}
|
||||
/>
|
||||
</TableField>
|
||||
)
|
||||
}
|
||||
|
||||
const turnCountField = () => {
|
||||
return (
|
||||
<InputTableField
|
||||
name="turn_count"
|
||||
className="Numeric"
|
||||
label={t('modals.edit_team.labels.turn_count')}
|
||||
placeholder="0"
|
||||
type="number"
|
||||
value={turnCount}
|
||||
onValueChange={handleTurnCountChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const buttonCountField = () => {
|
||||
return (
|
||||
<InputTableField
|
||||
name="button_count"
|
||||
className="Numeric"
|
||||
label={t('modals.edit_team.labels.button_count')}
|
||||
placeholder="0"
|
||||
type="number"
|
||||
value={buttonCount}
|
||||
onValueChange={handleButtonCountChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const chainCountField = () => {
|
||||
return (
|
||||
<InputTableField
|
||||
name="chain_count"
|
||||
className="Numeric"
|
||||
label={t('modals.edit_team.labels.chain_count')}
|
||||
placeholder="0"
|
||||
type="number"
|
||||
value={chainCount}
|
||||
onValueChange={handleChainCountChanged}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const infoPage = () => {
|
||||
return (
|
||||
<>
|
||||
{nameField()}
|
||||
{raidField()}
|
||||
{extraNotice()}
|
||||
{descriptionField()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const propertiesPage = () => {
|
||||
return (
|
||||
<>
|
||||
{chargeAttackField()}
|
||||
{fullAutoField()}
|
||||
{autoSummonField()}
|
||||
{autoGuardField()}
|
||||
{extraField()}
|
||||
{clearTimeField()}
|
||||
{turnCountField()}
|
||||
{buttonCountField()}
|
||||
{chainCountField()}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={openChange}>
|
||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||
<DialogContent
|
||||
className="EditTeam"
|
||||
headerref={headerRef}
|
||||
footerref={footerRef}
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<div className="DialogHeader" ref={headerRef}>
|
||||
<div className="DialogTop">
|
||||
<DialogTitle className="DialogTitle">
|
||||
{t('modals.edit_team.title')}
|
||||
</DialogTitle>
|
||||
</div>
|
||||
<DialogClose className="DialogClose" asChild>
|
||||
<span>
|
||||
<CrossIcon />
|
||||
</span>
|
||||
</DialogClose>
|
||||
</div>
|
||||
|
||||
<div className="Content">
|
||||
{segmentedControl()}
|
||||
<div className="Fields">
|
||||
{currentSegment === 0 && infoPage()}
|
||||
{currentSegment === 1 && propertiesPage()}
|
||||
</div>
|
||||
</div>
|
||||
<div className="DialogFooter" ref={footerRef}>
|
||||
<div className="Left"></div>
|
||||
<div className="Right Buttons Spaced">
|
||||
<Button
|
||||
contained={true}
|
||||
text={t('buttons.cancel')}
|
||||
onClick={openChange}
|
||||
/>
|
||||
<Button
|
||||
contained={true}
|
||||
rightAccessoryIcon={<CheckIcon />}
|
||||
text={t('modals.edit_team.buttons.confirm')}
|
||||
onClick={updateDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditPartyModal
|
||||
|
|
@ -119,6 +119,23 @@ const Party = (props: Props) => {
|
|||
.then((response) => storeParty(response.data.party))
|
||||
}
|
||||
|
||||
async function updateParty(details: DetailsObject) {
|
||||
const payload = formatDetailsObject(details)
|
||||
|
||||
if (props.team && props.team.id) {
|
||||
return await api.endpoints.parties
|
||||
.update(props.team.id, payload)
|
||||
.then((response) => storeParty(response.data.party))
|
||||
.catch((error) => {
|
||||
const data = error.response.data
|
||||
if (data.errors && Object.keys(data.errors).includes('guidebooks')) {
|
||||
const message = t('errors.validation.guidebooks')
|
||||
setErrorMessage(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Methods: Updating the party's details
|
||||
async function updateDetails(details: DetailsObject) {
|
||||
if (!props.team) return await createParty(details)
|
||||
|
|
@ -131,10 +148,10 @@ const Party = (props: Props) => {
|
|||
const mappings: { [key: string]: string } = {
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
raid: 'raid_id',
|
||||
chargeAttack: 'charge_attack',
|
||||
fullAuto: 'full_auto',
|
||||
autoGuard: 'auto_guard',
|
||||
autoSummon: 'auto_summon',
|
||||
clearTime: 'clear_time',
|
||||
buttonCount: 'button_count',
|
||||
chainCount: 'chain_count',
|
||||
|
|
@ -152,6 +169,8 @@ const Party = (props: Props) => {
|
|||
}
|
||||
})
|
||||
|
||||
if (details.raid) payload.raid_id = details.raid.id
|
||||
|
||||
if (Object.keys(payload).length >= 1) {
|
||||
return { party: payload }
|
||||
} else {
|
||||
|
|
@ -159,23 +178,6 @@ const Party = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
async function updateParty(details: DetailsObject) {
|
||||
const payload = formatDetailsObject(details)
|
||||
|
||||
if (props.team && props.team.id) {
|
||||
return await api.endpoints.parties
|
||||
.update(props.team.id, payload)
|
||||
.then((response) => storeParty(response.data.party))
|
||||
.catch((error) => {
|
||||
const data = error.response.data
|
||||
if (data.errors && Object.keys(data.errors).includes('guidebooks')) {
|
||||
const message = t('errors.validation.guidebooks')
|
||||
setErrorMessage(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAlert() {
|
||||
setErrorMessage('')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import clonedeep from 'lodash.clonedeep'
|
||||
|
||||
|
|
@ -9,23 +8,13 @@ import LiteYouTubeEmbed from 'react-lite-youtube-embed'
|
|||
import classNames from 'classnames'
|
||||
import reactStringReplace from 'react-string-replace'
|
||||
|
||||
import Button from '~components/common/Button'
|
||||
import GridRepCollection from '~components/GridRepCollection'
|
||||
import GridRep from '~components/GridRep'
|
||||
import Tooltip from '~components/common/Tooltip'
|
||||
import TextFieldset from '~components/common/TextFieldset'
|
||||
|
||||
import api from '~utils/api'
|
||||
import { appState, initialAppState } from '~utils/appState'
|
||||
import { formatTimeAgo } from '~utils/timeAgo'
|
||||
import { appState } from '~utils/appState'
|
||||
import { youtube } from '~utils/youtube'
|
||||
|
||||
import CheckIcon from '~public/icons/Check.svg'
|
||||
import CrossIcon from '~public/icons/Cross.svg'
|
||||
import EllipsisIcon from '~public/icons/Ellipsis.svg'
|
||||
import EditIcon from '~public/icons/Edit.svg'
|
||||
import RemixIcon from '~public/icons/Remix.svg'
|
||||
|
||||
import type { DetailsObject } from 'types'
|
||||
|
||||
import './index.scss'
|
||||
|
|
@ -45,10 +34,7 @@ const PartyDetails = (props: Props) => {
|
|||
const youtubeUrlRegex =
|
||||
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
|
||||
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [alertOpen, setAlertOpen] = useState(false)
|
||||
|
||||
const [remixes, setRemixes] = useState<Party[]>([])
|
||||
const [embeddedDescription, setEmbeddedDescription] =
|
||||
|
|
@ -60,17 +46,6 @@ const PartyDetails = (props: Props) => {
|
|||
Visible: !open,
|
||||
})
|
||||
|
||||
const editableClasses = classNames({
|
||||
PartyDetails: true,
|
||||
Editable: true,
|
||||
Visible: open,
|
||||
})
|
||||
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||||
name: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
// Extract the video IDs from the description
|
||||
if (appState.party.description) {
|
||||
|
|
@ -104,46 +79,12 @@ const PartyDetails = (props: Props) => {
|
|||
}
|
||||
}, [appState.party.description])
|
||||
|
||||
function handleTextAreaChange(event: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
let newErrors = errors
|
||||
|
||||
setErrors(newErrors)
|
||||
}
|
||||
|
||||
async function fetchYoutubeData(videoId: string) {
|
||||
return await youtube
|
||||
.getVideoById(videoId, { maxResults: 1 })
|
||||
.then((data) => data.items[0].snippet.localized.title)
|
||||
}
|
||||
|
||||
function toggleDetails() {
|
||||
// Enabling this code will make live updates not work,
|
||||
// but I'm not sure why it's here, so we're not going to remove it.
|
||||
|
||||
// if (name !== party.name) {
|
||||
// const resetName = party.name ? party.name : ''
|
||||
// setName(resetName)
|
||||
// if (nameInput.current) nameInput.current.value = resetName
|
||||
// }
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
function updateDetails(event: React.MouseEvent) {
|
||||
const details: DetailsObject = {
|
||||
description: descriptionInput.current?.value,
|
||||
}
|
||||
|
||||
props.updateCallback(details)
|
||||
toggleDetails()
|
||||
}
|
||||
|
||||
function handleClick() {
|
||||
setAlertOpen(!alertOpen)
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function goTo(shortcode?: string) {
|
||||
if (shortcode) router.push(`/p/${shortcode}`)
|
||||
|
|
@ -232,46 +173,6 @@ const PartyDetails = (props: Props) => {
|
|||
})
|
||||
}
|
||||
|
||||
const editable = () => {
|
||||
return (
|
||||
<section className={editableClasses}>
|
||||
<TextFieldset
|
||||
fieldName="name"
|
||||
placeholder={
|
||||
'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}
|
||||
error={errors.description}
|
||||
ref={descriptionInput}
|
||||
/>
|
||||
|
||||
<div className="bottom">
|
||||
<div className="left">
|
||||
{router.pathname !== '/new' ? (
|
||||
<Button
|
||||
leftAccessoryIcon={<CrossIcon />}
|
||||
className="Blended medium destructive"
|
||||
onClick={handleClick}
|
||||
text={t('buttons.delete')}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
<div className="right">
|
||||
<Button text={t('buttons.cancel')} onClick={toggleDetails} />
|
||||
<Button
|
||||
leftAccessoryIcon={<CheckIcon className="Check" />}
|
||||
text={t('buttons.save_info')}
|
||||
onClick={updateDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const readOnly = () => {
|
||||
return (
|
||||
<section className={readOnlyClasses}>
|
||||
|
|
@ -291,10 +192,7 @@ const PartyDetails = (props: Props) => {
|
|||
|
||||
return (
|
||||
<>
|
||||
<section className="DetailsWrapper">
|
||||
{readOnly()}
|
||||
{editable()}
|
||||
</section>
|
||||
<section className="DetailsWrapper">{readOnly()}</section>
|
||||
{remixes && remixes.length > 0 ? remixSection() : ''}
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
|
|
@ -6,21 +6,16 @@ import { useTranslation } from 'next-i18next'
|
|||
import classNames from 'classnames'
|
||||
|
||||
import Button from '~components/common/Button'
|
||||
import CharLimitedFieldset from '~components/common/CharLimitedFieldset'
|
||||
import DurationInput from '~components/common/DurationInput'
|
||||
import Input from '~components/common/Input'
|
||||
import RaidCombobox from '~components/raids/RaidCombobox'
|
||||
import Switch from '~components/common/Switch'
|
||||
import Tooltip from '~components/common/Tooltip'
|
||||
import Token from '~components/common/Token'
|
||||
|
||||
import EditPartyModal from '~components/party/EditPartyModal'
|
||||
import PartyDropdown from '~components/party/PartyDropdown'
|
||||
|
||||
import { accountState } from '~utils/accountState'
|
||||
import { appState, initialAppState } from '~utils/appState'
|
||||
import { formatTimeAgo } from '~utils/timeAgo'
|
||||
|
||||
import CheckIcon from '~public/icons/Check.svg'
|
||||
import EditIcon from '~public/icons/Edit.svg'
|
||||
import RemixIcon from '~public/icons/Remix.svg'
|
||||
import SaveIcon from '~public/icons/Save.svg'
|
||||
|
|
@ -41,7 +36,7 @@ interface Props {
|
|||
}
|
||||
|
||||
const PartyHeader = (props: Props) => {
|
||||
const { party, raids } = useSnapshot(appState)
|
||||
const { party } = useSnapshot(appState)
|
||||
|
||||
const { t } = useTranslation('common')
|
||||
const router = useRouter()
|
||||
|
|
@ -49,12 +44,7 @@ const PartyHeader = (props: Props) => {
|
|||
|
||||
const { party: partySnapshot } = useSnapshot(appState)
|
||||
|
||||
const nameInput = React.createRef<HTMLInputElement>()
|
||||
const descriptionInput = React.createRef<HTMLTextAreaElement>()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [name, setName] = useState('')
|
||||
const [alertOpen, setAlertOpen] = useState(false)
|
||||
|
||||
const [chargeAttack, setChargeAttack] = useState(true)
|
||||
const [fullAuto, setFullAuto] = useState(false)
|
||||
|
|
@ -65,18 +55,9 @@ const PartyHeader = (props: Props) => {
|
|||
const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
|
||||
const [clearTime, setClearTime] = useState(0)
|
||||
|
||||
const [raidSlug, setRaidSlug] = useState('')
|
||||
|
||||
const readOnlyClasses = classNames({
|
||||
const classes = classNames({
|
||||
PartyDetails: true,
|
||||
ReadOnly: true,
|
||||
Visible: !open,
|
||||
})
|
||||
|
||||
const editableClasses = classNames({
|
||||
PartyDetails: true,
|
||||
Editable: true,
|
||||
Visible: open,
|
||||
})
|
||||
|
||||
const userClass = classNames({
|
||||
|
|
@ -93,11 +74,6 @@ const PartyHeader = (props: Props) => {
|
|||
light: party && party.element == 6,
|
||||
})
|
||||
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||||
name: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (props.party) {
|
||||
setName(props.party.name)
|
||||
|
|
@ -130,112 +106,6 @@ const PartyHeader = (props: Props) => {
|
|||
})
|
||||
}, [])
|
||||
|
||||
function handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
|
||||
event.preventDefault()
|
||||
|
||||
const { name, value } = event.target
|
||||
setName(value)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDetails() {
|
||||
// Enabling this code will make live updates not work,
|
||||
// but I'm not sure why it's here, so we're not going to remove it.
|
||||
|
||||
// if (name !== party.name) {
|
||||
// const resetName = party.name ? party.name : ''
|
||||
// setName(resetName)
|
||||
// if (nameInput.current) nameInput.current.value = resetName
|
||||
// }
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
function receiveRaid(raid?: Raid) {
|
||||
if (raid) setRaidSlug(raid?.slug)
|
||||
}
|
||||
|
||||
function switchValue(value: boolean) {
|
||||
if (value) return 'on'
|
||||
else return 'off'
|
||||
}
|
||||
|
||||
// Actions: Favorites
|
||||
function toggleFavorite() {
|
||||
if (appState.party.favorited) unsaveFavorite()
|
||||
|
|
@ -258,28 +128,6 @@ const PartyHeader = (props: Props) => {
|
|||
else console.error('Failed to unsave team: No party ID')
|
||||
}
|
||||
|
||||
function updateDetails(event: React.MouseEvent) {
|
||||
const descriptionValue = descriptionInput.current?.value
|
||||
const allRaids = appState.raidGroups.flatMap((group) => group.raids)
|
||||
const raid = allRaids.find((raid) => raid.slug === raidSlug)
|
||||
|
||||
const details: DetailsObject = {
|
||||
fullAuto: fullAuto,
|
||||
autoGuard: autoGuard,
|
||||
chargeAttack: chargeAttack,
|
||||
clearTime: clearTime,
|
||||
buttonCount: buttonCount,
|
||||
turnCount: turnCount,
|
||||
chainCount: chainCount,
|
||||
name: name,
|
||||
description: descriptionValue,
|
||||
raid: raid,
|
||||
}
|
||||
|
||||
props.updateCallback(details)
|
||||
toggleDetails()
|
||||
}
|
||||
|
||||
// Methods: Navigation
|
||||
function goTo(shortcode?: string) {
|
||||
if (shortcode) router.push(`/p/${shortcode}`)
|
||||
|
|
@ -487,145 +335,6 @@ const PartyHeader = (props: Props) => {
|
|||
</Tooltip>
|
||||
)
|
||||
}
|
||||
const editable = () => {
|
||||
return (
|
||||
<section className={editableClasses}>
|
||||
<CharLimitedFieldset
|
||||
fieldName="name"
|
||||
placeholder="Name your team"
|
||||
value={props.party?.name}
|
||||
limit={50}
|
||||
onChange={handleInputChange}
|
||||
error={errors.name}
|
||||
ref={nameInput}
|
||||
/>
|
||||
<RaidCombobox
|
||||
showAllRaidsOption={false}
|
||||
currentRaid={props.party?.raid ? props.party?.raid : 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>
|
||||
|
||||
<div className="bottom">
|
||||
<div className="right">
|
||||
<Button text={t('buttons.cancel')} onClick={toggleDetails} />
|
||||
<Button
|
||||
leftAccessoryIcon={<CheckIcon className="Check" />}
|
||||
text={t('buttons.save_info')}
|
||||
onClick={updateDetails}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
const readOnly = () => {
|
||||
return <section className={readOnlyClasses}>{renderTokens()}</section>
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
@ -666,11 +375,15 @@ const PartyHeader = (props: Props) => {
|
|||
</div>
|
||||
{party.editable ? (
|
||||
<div className="Right">
|
||||
<Button
|
||||
leftAccessoryIcon={<EditIcon />}
|
||||
text={t('buttons.show_info')}
|
||||
onClick={toggleDetails}
|
||||
/>
|
||||
<EditPartyModal
|
||||
party={props.party}
|
||||
updateCallback={props.updateCallback}
|
||||
>
|
||||
<Button
|
||||
leftAccessoryIcon={<EditIcon />}
|
||||
text={t('buttons.show_info')}
|
||||
/>
|
||||
</EditPartyModal>
|
||||
<PartyDropdown
|
||||
editable={props.editable}
|
||||
deleteTeamCallback={props.deleteCallback}
|
||||
|
|
@ -684,8 +397,7 @@ const PartyHeader = (props: Props) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{readOnly()}
|
||||
{editable()}
|
||||
<section className={classes}>{renderTokens()}</section>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
.Raids {
|
||||
border-bottom-left-radius: $card-corner;
|
||||
border-bottom-right-radius: $card-corner;
|
||||
height: 50vh;
|
||||
height: 36vh;
|
||||
overflow-y: scroll;
|
||||
padding: 0 $unit;
|
||||
|
||||
|
|
@ -120,7 +120,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
.DetailsWrapper .PartyDetails.Editable .Raid.SelectTrigger {
|
||||
.DetailsWrapper .PartyDetails.Editable .Raid.SelectTrigger,
|
||||
.EditTeam .Raid.SelectTrigger {
|
||||
background: var(--input-bound-bg);
|
||||
display: flex;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 11px;
|
||||
|
|
@ -139,9 +141,9 @@
|
|||
}
|
||||
|
||||
.ExtraIndicator {
|
||||
background: var(--extra-purple-bg);
|
||||
background: var(--extra-purple-secondary);
|
||||
border-radius: $full-corner;
|
||||
color: var(--extra-purple-text);
|
||||
color: $grey-100;
|
||||
display: flex;
|
||||
font-weight: $bold;
|
||||
font-size: $font-tiny;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface Props {
|
|||
currentRaid?: Raid
|
||||
defaultRaid?: Raid
|
||||
minimal?: boolean
|
||||
tabIndex?: number
|
||||
onChange?: (raid?: Raid) => void
|
||||
onBlur?: (event: React.ChangeEvent<HTMLSelectElement>) => void
|
||||
}
|
||||
|
|
@ -46,6 +47,7 @@ const untitledGroup: RaidGroup = {
|
|||
section: 0,
|
||||
order: 0,
|
||||
extra: false,
|
||||
guidebooks: false,
|
||||
raids: [],
|
||||
difficulty: 0,
|
||||
hl: false,
|
||||
|
|
@ -114,7 +116,6 @@ const RaidCombobox = (props: Props) => {
|
|||
// Set current raid and section when the current raid changes
|
||||
useEffect(() => {
|
||||
if (props.currentRaid) {
|
||||
console.log('We are here with a raid')
|
||||
setCurrentRaid(props.currentRaid)
|
||||
setCurrentSection(props.currentRaid.group.section)
|
||||
}
|
||||
|
|
@ -183,7 +184,6 @@ const RaidCombobox = (props: Props) => {
|
|||
const { top: itemTop } = node.getBoundingClientRect()
|
||||
|
||||
listRef.current.scrollTop = itemTop - listTop
|
||||
console.log('Focusing node')
|
||||
node.focus()
|
||||
setScrolled(true)
|
||||
}
|
||||
|
|
@ -534,6 +534,7 @@ const RaidCombobox = (props: Props) => {
|
|||
onOpenChange={toggleOpen}
|
||||
placeholder={t('raids.placeholder')}
|
||||
trigger={{ className: 'Raid' }}
|
||||
triggerTabIndex={props.tabIndex}
|
||||
value={renderTriggerContent()}
|
||||
>
|
||||
<Command className="Raid Combobox">
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
&:hover {
|
||||
.ExtraIndicator {
|
||||
background: var(--extra-purple-primary);
|
||||
background: var(--extra-purple-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
|
@ -15,6 +15,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.Selected .ExtraIndicator {
|
||||
background: var(--extra-purple-secondary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.Text {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -377,23 +377,24 @@ const WeaponGrid = (props: Props) => {
|
|||
|
||||
const extraElement = (
|
||||
<ExtraContainer>
|
||||
<ExtraWeaponsGrid
|
||||
grid={appState.grid.weapons.allWeapons}
|
||||
enabled={appState.party.extra}
|
||||
editable={props.editable}
|
||||
offset={numWeapons}
|
||||
removeWeapon={removeWeapon}
|
||||
updateExtra={props.updateExtra}
|
||||
updateObject={receiveWeaponFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
<GuidebooksGrid
|
||||
grid={appState.party.guidebooks}
|
||||
editable={props.editable}
|
||||
offset={numWeapons}
|
||||
removeGuidebook={removeGuidebook}
|
||||
updateObject={receiveGuidebookFromSearch}
|
||||
/>
|
||||
{appState.party.raid && appState.party.raid.group.extra && (
|
||||
<ExtraWeaponsGrid
|
||||
grid={appState.grid.weapons.allWeapons}
|
||||
editable={props.editable}
|
||||
offset={numWeapons}
|
||||
removeWeapon={removeWeapon}
|
||||
updateObject={receiveWeaponFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
/>
|
||||
)}
|
||||
{appState.party.raid && appState.party.raid.group.guidebooks && (
|
||||
<GuidebooksGrid
|
||||
grid={appState.party.guidebooks}
|
||||
editable={props.editable}
|
||||
removeGuidebook={removeGuidebook}
|
||||
updateObject={receiveGuidebookFromSearch}
|
||||
/>
|
||||
)}
|
||||
</ExtraContainer>
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -169,6 +169,39 @@
|
|||
"confirm": "Confirm"
|
||||
}
|
||||
},
|
||||
"edit_team": {
|
||||
"title": "Edit team details",
|
||||
"segments": {
|
||||
"basic_info": "Basic info",
|
||||
"properties": "Battle settings"
|
||||
},
|
||||
"buttons": {
|
||||
"add_field": "Add field",
|
||||
"confirm": "Save details"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Name",
|
||||
"description": "Description",
|
||||
"raid": "Battle",
|
||||
"extra": "Additional weapons",
|
||||
"charge_attack": "Charge Attack",
|
||||
"full_auto": "Full Auto",
|
||||
"auto_guard": "Auto Guard",
|
||||
"auto_summon": "Auto Summon",
|
||||
"turn_count": "Number of turns",
|
||||
"button_count": "Number of buttons pressed",
|
||||
"chain_count": "Number of chains",
|
||||
"clear_time": "Clear time"
|
||||
},
|
||||
"descriptions": {
|
||||
"extra": "Additional weapons are controlled by the selected battle"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "Name your team"
|
||||
},
|
||||
"extra_notice": "You can add additional weapons to this team",
|
||||
"extra_notice_guidebooks": "You can add additional weapons and Sephira Guidebooks to this team"
|
||||
},
|
||||
"delete_team": {
|
||||
"title": "Delete team",
|
||||
"description": "Are you sure you want to permanently delete this team?",
|
||||
|
|
|
|||
|
|
@ -169,6 +169,35 @@
|
|||
"confirm": "入れ替える"
|
||||
}
|
||||
},
|
||||
"edit_team": {
|
||||
"title": "編成の詳細を編集",
|
||||
"segments": {
|
||||
"basic_info": "基本情報",
|
||||
"properties": "バトル設定・結果"
|
||||
},
|
||||
"buttons": {
|
||||
"add_field": "フィールドを追加",
|
||||
"confirm": "詳細を保存する"
|
||||
},
|
||||
"labels": {
|
||||
"name": "編成名",
|
||||
"description": "説明",
|
||||
"raid": "バトル",
|
||||
"charge_attack": "奥義",
|
||||
"full_auto": "フルオート",
|
||||
"auto_guard": "オートガード",
|
||||
"auto_summon": "オート召喚",
|
||||
"turn_count": "経過ターン",
|
||||
"button_count": "ポチ数",
|
||||
"chain_count": "チェイン数",
|
||||
"clear_time": "討伐時間"
|
||||
},
|
||||
"placeholders": {
|
||||
"name": "編成に名前を付ける"
|
||||
},
|
||||
"extra_notice": "Additional Weapons枠に武器を装備することはできます",
|
||||
"extra_notice_guidebooks": "Additional Weaponsまたはセフィラ導本を装備することはできます"
|
||||
},
|
||||
"delete_team": {
|
||||
"title": "編成を削除しますか",
|
||||
"description": "編成を削除する操作は取り消せません。",
|
||||
|
|
|
|||
1
types/Party.d.ts
vendored
1
types/Party.d.ts
vendored
|
|
@ -20,6 +20,7 @@ interface Party {
|
|||
raid: Raid
|
||||
full_auto: boolean
|
||||
auto_guard: boolean
|
||||
auto_summon: boolean
|
||||
charge_attack: boolean
|
||||
clear_time: number
|
||||
button_count?: number
|
||||
|
|
|
|||
1
types/RaidGroup.d.ts
vendored
1
types/RaidGroup.d.ts
vendored
|
|
@ -10,5 +10,6 @@ interface RaidGroup {
|
|||
section: number
|
||||
order: number
|
||||
extra: boolean
|
||||
guidebooks: boolean
|
||||
hl: boolean
|
||||
}
|
||||
|
|
|
|||
1
types/index.d.ts
vendored
1
types/index.d.ts
vendored
|
|
@ -29,6 +29,7 @@ export type DetailsObject = {
|
|||
[key: string]: boolean | number | string | string[] | Raid | undefined
|
||||
fullAuto?: boolean
|
||||
autoGuard?: boolean
|
||||
autoSummon?: boolean
|
||||
chargeAttack?: boolean
|
||||
clearTime?: number
|
||||
buttonCount?: number
|
||||
|
|
|
|||
Loading…
Reference in a new issue