hensei-web/components/party/Party/index.tsx
Justin Edmund 1c2a1b6bb4
Deploy organization and bug fixes (#299)
* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Added World Series to weapon series empty state (#293)

* Push 2023/03 updates to main (#292)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Add World series to empty state

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Enables advanced filters in collections (#289)

* Add skeleton of FilterModal

* Install react-slider from Radix

* Move AccountModal styles to more generic place

* Make generic TableField and move styles

This is so we have a base for other table rows that use different interactive elements

* Implement custom Slider component

This inherits from Radix's Slider

* Implement SliderTableField

* Implemented SwitchTableField

* Change enabled switch color

* Implement InputTableField

* Update modal skeleton

* Added localizations for Advanced filters

* Update styles for various components

Added some new colors and fixed spacing

* Added value reporting and fixed a cycle error

* Added default values, clearing filters, etc

* Default values
* Ability to clear filters
* Receiving values from components

* Fix maximum cycle depth exceeded error

* Update TableFields to not error

Also optional value is required

* Create FilterSet.d.ts

* Send filtersets to FilterModal

This sends the default filterset and the user's filterset to the filter modal.

The default filterset is used when resetting all filters. The users filterset is used so that it is populated with the user's values when they open the modal

* Add new localizations

* Change types and add default filterset object

* Add fast-deep-equal package

* Change value in table fields

* Input table fields need to be able to be empty
* Slider table fields should default to 0 if value isn't provided

* Set width of Select in table field in Filter dialog

* Add style for filter button with filters active

* Swap to using selects for some boolean fields

Charge Attack, Full Auto, and Auto Guard are not boolean values since the user can select (and the default should be) to show both on and off values. We swap to using a SelectTableField here to represent this difference.

We also added logic for Full Auto and Auto Guard fields since they are tied together in some cases (you can't show Auto Guard teams that have Full Auto disabled)

* Populate values from defaultFilterSet

* Update how we save and propagate filters

We save filterset in a local state, because the FilterBar will send it down to us from cookies.

We then set each individual property from that filter set.

We set inputs to have a placeholder, as max buttons and max turns could not be set (null). Then, we only send those fields when they have a value provided by the user.

* Remove default filterset

This was moved to a utils/ file

* Propagate filters from modal

This updates how we handle filter propagation to accommodate the advanced ones. The icon lights up when filters are active.

* Implement advanced filters on Teams page

* Add skeleton of FilterModal

* Make generic TableField and move styles

This is so we have a base for other table rows that use different interactive elements

* Implement custom Slider component

This inherits from Radix's Slider

* Implement SliderTableField

* Implemented SwitchTableField

* Implement InputTableField

* Update modal skeleton

* Added localizations for Advanced filters

* Update styles for various components

Added some new colors and fixed spacing

* Added value reporting and fixed a cycle error

* Added default values, clearing filters, etc

* Default values
* Ability to clear filters
* Receiving values from components

* Fix maximum cycle depth exceeded error

* Update TableFields to not error

Also optional value is required

* Create FilterSet.d.ts

* Send filtersets to FilterModal

This sends the default filterset and the user's filterset to the filter modal.

The default filterset is used when resetting all filters. The users filterset is used so that it is populated with the user's values when they open the modal

* Add new localizations

* Change types and add default filterset object

* Change value in table fields

* Input table fields need to be able to be empty
* Slider table fields should default to 0 if value isn't provided

* Set width of Select in table field in Filter dialog

* Add style for filter button with filters active

* Swap to using selects for some boolean fields

Charge Attack, Full Auto, and Auto Guard are not boolean values since the user can select (and the default should be) to show both on and off values. We swap to using a SelectTableField here to represent this difference.

We also added logic for Full Auto and Auto Guard fields since they are tied together in some cases (you can't show Auto Guard teams that have Full Auto disabled)

* Populate values from defaultFilterSet

* Update how we save and propagate filters

We save filterset in a local state, because the FilterBar will send it down to us from cookies.

We then set each individual property from that filter set.

We set inputs to have a placeholder, as max buttons and max turns could not be set (null). Then, we only send those fields when they have a value provided by the user.

* Remove default filterset

This was moved to a utils/ file

* Propagate filters from modal

This updates how we handle filter propagation to accommodate the advanced ones. The icon lights up when filters are active.

* GridRep adjustments

* Properly unset mainhand when cells get reused and the new team doesnt have one
* Slightly better styling to make the grid more correct

* Fix bad merge

* Add advanced filter support to saved and profile pages

* Fix auto guard text

* Ensure fetchTeams callback is updated with filters

* Add auto guard icon to GridRep

* Disable max buttons and turns

* Fix build errors

* Organize components (#298)

* Deploy advanced filters (#297)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Added World Series to weapon series empty state (#293)

* Push 2023/03 updates to main (#292)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Add World series to empty state

* Added items from 2023/03 Legfest and 2023/03/30 update (#290)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Missed items (#291)

* Added avatars (#286)

* Deploy #287 (#288)

* Added avatars

* Added content from the 2023/03/22 update (#287)

* Added avatars (#286)

* Added localizations

* Added update, changed CSS

* Add logic for showing Lucifer uncap and 250 art

* Added new weapon series

* Added updates

* Add more items

* Enables advanced filters in collections (#289)

* Add skeleton of FilterModal

* Install react-slider from Radix

* Move AccountModal styles to more generic place

* Make generic TableField and move styles

This is so we have a base for other table rows that use different interactive elements

* Implement custom Slider component

This inherits from Radix's Slider

* Implement SliderTableField

* Implemented SwitchTableField

* Change enabled switch color

* Implement InputTableField

* Update modal skeleton

* Added localizations for Advanced filters

* Update styles for various components

Added some new colors and fixed spacing

* Added value reporting and fixed a cycle error

* Added default values, clearing filters, etc

* Default values
* Ability to clear filters
* Receiving values from components

* Fix maximum cycle depth exceeded error

* Update TableFields to not error

Also optional value is required

* Create FilterSet.d.ts

* Send filtersets to FilterModal

This sends the default filterset and the user's filterset to the filter modal.

The default filterset is used when resetting all filters. The users filterset is used so that it is populated with the user's values when they open the modal

* Add new localizations

* Change types and add default filterset object

* Add fast-deep-equal package

* Change value in table fields

* Input table fields need to be able to be empty
* Slider table fields should default to 0 if value isn't provided

* Set width of Select in table field in Filter dialog

* Add style for filter button with filters active

* Swap to using selects for some boolean fields

Charge Attack, Full Auto, and Auto Guard are not boolean values since the user can select (and the default should be) to show both on and off values. We swap to using a SelectTableField here to represent this difference.

We also added logic for Full Auto and Auto Guard fields since they are tied together in some cases (you can't show Auto Guard teams that have Full Auto disabled)

* Populate values from defaultFilterSet

* Update how we save and propagate filters

We save filterset in a local state, because the FilterBar will send it down to us from cookies.

We then set each individual property from that filter set.

We set inputs to have a placeholder, as max buttons and max turns could not be set (null). Then, we only send those fields when they have a value provided by the user.

* Remove default filterset

This was moved to a utils/ file

* Propagate filters from modal

This updates how we handle filter propagation to accommodate the advanced ones. The icon lights up when filters are active.

* Implement advanced filters on Teams page

* Add skeleton of FilterModal

* Make generic TableField and move styles

This is so we have a base for other table rows that use different interactive elements

* Implement custom Slider component

This inherits from Radix's Slider

* Implement SliderTableField

* Implemented SwitchTableField

* Implement InputTableField

* Update modal skeleton

* Added localizations for Advanced filters

* Update styles for various components

Added some new colors and fixed spacing

* Added value reporting and fixed a cycle error

* Added default values, clearing filters, etc

* Default values
* Ability to clear filters
* Receiving values from components

* Fix maximum cycle depth exceeded error

* Update TableFields to not error

Also optional value is required

* Create FilterSet.d.ts

* Send filtersets to FilterModal

This sends the default filterset and the user's filterset to the filter modal.

The default filterset is used when resetting all filters. The users filterset is used so that it is populated with the user's values when they open the modal

* Add new localizations

* Change types and add default filterset object

* Change value in table fields

* Input table fields need to be able to be empty
* Slider table fields should default to 0 if value isn't provided

* Set width of Select in table field in Filter dialog

* Add style for filter button with filters active

* Swap to using selects for some boolean fields

Charge Attack, Full Auto, and Auto Guard are not boolean values since the user can select (and the default should be) to show both on and off values. We swap to using a SelectTableField here to represent this difference.

We also added logic for Full Auto and Auto Guard fields since they are tied together in some cases (you can't show Auto Guard teams that have Full Auto disabled)

* Populate values from defaultFilterSet

* Update how we save and propagate filters

We save filterset in a local state, because the FilterBar will send it down to us from cookies.

We then set each individual property from that filter set.

We set inputs to have a placeholder, as max buttons and max turns could not be set (null). Then, we only send those fields when they have a value provided by the user.

* Remove default filterset

This was moved to a utils/ file

* Propagate filters from modal

This updates how we handle filter propagation to accommodate the advanced ones. The icon lights up when filters are active.

* GridRep adjustments

* Properly unset mainhand when cells get reused and the new team doesnt have one
* Slightly better styling to make the grid more correct

* Fix bad merge

* Add advanced filter support to saved and profile pages

* Fix auto guard text

* Ensure fetchTeams callback is updated with filters

* Add auto guard icon to GridRep

* Disable max buttons and turns

* Fix build errors

* Organize components into folders

* Fix transcendence popover levels

* Remove extra styles

* Add Storybook

* Delete Home.module.css

* Update paths

* Fix popover arrow

* Fix import paths

* Fix scrollbars in search

* Add type to further fix TranscendencePopover

* Add background-size to 1x in hidpiImage mixin

* Fix hovercard scrollbar

* Move components

* Fix ElementToggle on smaller devices

* Change default filterset

Min characters should be 2, not 3
2023-04-12 06:37:41 -07:00

366 lines
10 KiB
TypeScript

import React, { useEffect, useState } from 'react'
import { getCookie } from 'cookies-next'
import { useRouter } from 'next/router'
import { subscribe, useSnapshot } from 'valtio'
import clonedeep from 'lodash.clonedeep'
import PartySegmentedControl from '~components/party/PartySegmentedControl'
import PartyDetails from '~components/party/PartyDetails'
import WeaponGrid from '~components/weapon/WeaponGrid'
import SummonGrid from '~components/summon/SummonGrid'
import CharacterGrid from '~components/character/CharacterGrid'
import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import { getLocalId } from '~utils/localId'
import { GridType } from '~utils/enums'
import { retrieveCookies } from '~utils/retrieveCookies'
import { setEditKey, storeEditKey, unsetEditKey } from '~utils/userToken'
import type { DetailsObject } from '~types'
import './index.scss'
// Props
interface Props {
new?: boolean
team?: Party
raids: Raid[][]
selectedTab: GridType
pushHistory?: (path: string) => void
}
const defaultProps = {
selectedTab: GridType.Weapon,
}
const Party = (props: Props) => {
// Set up router
const router = useRouter()
// Set up states
const { party } = useSnapshot(appState)
const [editable, setEditable] = useState(false)
const [currentTab, setCurrentTab] = useState<GridType>(GridType.Weapon)
const [refresh, setRefresh] = useState(false)
// Retrieve cookies
const cookies = retrieveCookies()
// Reset state on first load
useEffect(() => {
const resetState = clonedeep(initialAppState)
appState.grid = resetState.grid
if (props.team) storeParty(props.team)
}, [])
// Subscribe to app state to listen for account changes and
// unsubscribe when component is unmounted
const unsubscribe = subscribe(accountState, () => {
setRefresh(true)
})
useEffect(() => () => unsubscribe(), [])
// Set editable on first load
useEffect(() => {
// Get cookie
const cookie = getCookie('account')
const accountData: AccountCookie = cookie
? JSON.parse(cookie as string)
: null
let editable = false
unsetEditKey()
if (props.new) editable = true
if (accountData && props.team && !props.new) {
if (accountData.token) {
// Authenticated
if (props.team.user && accountData.userId === props.team.user.id) {
editable = true
}
} else {
// Not authenticated
if (!props.team.user && accountData.userId === props.team.local_id) {
// Set editable
editable = true
// Also set edit key header
setEditKey(props.team.id, props.team.user)
}
}
}
appState.party.editable = editable
setEditable(editable)
}, [refresh])
// Set selected tab from props
useEffect(() => {
setCurrentTab(props.selectedTab)
}, [props.selectedTab])
// Methods: Creating a new party
async function createParty(details?: DetailsObject) {
let payload = {}
if (details) payload = formatDetailsObject(details)
return await api.endpoints.parties
.create({ ...payload, ...getLocalId() })
.then((response) => storeParty(response.data.party))
}
// Methods: Updating the party's details
async function updateDetails(details: DetailsObject) {
if (!props.team) return await createParty(details)
else updateParty(details)
}
function formatDetailsObject(details: DetailsObject) {
const payload: { [key: string]: any } = {}
if (details.name) payload.name = details.name
if (details.description) payload.description = details.description
if (details.raid) payload.raid_id = details.raid.id
if (details.chargeAttack) payload.charge_attack = details.chargeAttack
if (details.fullAuto) payload.full_auto = details.fullAuto
if (details.autoGuard) payload.auto_guard = details.autoGuard
if (details.clearTime) payload.clear_time = details.clearTime
if (details.buttonCount) payload.button_count = details.buttonCount
if (details.chainCount) payload.chain_count = details.chainCount
if (details.turnCount) payload.turn_count = details.turnCount
if (details.extra) payload.extra = details.extra
if (details.job) payload.job_id = details.job.id
if (Object.keys(payload).length > 1) return { party: payload }
else return {}
}
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))
}
}
function checkboxChanged(event: React.ChangeEvent<HTMLInputElement>) {
appState.party.extra = event.target.checked
// Only save if this is a saved party
if (props.team && props.team.id) {
api.endpoints.parties.update(props.team.id, {
party: { extra: event.target.checked },
})
}
}
// Deleting the party
function deleteTeam() {
if (props.team && editable) {
api.endpoints.parties
.destroy({ id: props.team.id })
.then(() => {
// Push to route
if (cookies && cookies.account.username) {
router.push(`/${cookies.account.username}`)
} else {
router.push('/')
}
// Clean state
const resetState = clonedeep(initialAppState)
Object.keys(resetState).forEach((key) => {
appState[key] = resetState[key]
})
// Set party to be editable
appState.party.editable = true
})
.catch((error) => {
console.error(error)
})
}
}
// Methods: Storing party data
const storeParty = function (team: any) {
// Store the important party and state-keeping values in global state
appState.party.name = team.name
appState.party.description = team.description
appState.party.raid = team.raid
appState.party.updated_at = team.updated_at
appState.party.job = team.job
appState.party.jobSkills = team.job_skills
appState.party.accessory = team.accessory
appState.party.id = team.id
appState.party.shortcode = team.shortcode
appState.party.extra = team.extra
appState.party.user = team.user
appState.party.favorited = team.favorited
appState.party.remix = team.remix
appState.party.remixes = team.remixes
appState.party.sourceParty = team.source_party
appState.party.created_at = team.created_at
appState.party.updated_at = team.updated_at
appState.party.detailsVisible = false
// Store the edit key in local storage
if (team.edit_key) {
storeEditKey(team.id, team.edit_key)
setEditKey(team.id, team.user)
}
// Populate state
storeCharacters(team.characters)
storeWeapons(team.weapons)
storeSummons(team.summons)
// Create a string to send the user back to the tab they're currently on
let tab = ''
if (currentTab === GridType.Character) {
tab = 'characters'
} else if (currentTab === GridType.Summon) {
tab = 'summons'
}
// Then, push the browser history to the new party's URL
if (props.pushHistory) {
props.pushHistory(`/p/${team.shortcode}/${tab}`)
}
return team
}
const storeCharacters = (list: Array<GridCharacter>) => {
list.forEach((object: GridCharacter) => {
if (object.position != null)
appState.grid.characters[object.position] = object
})
}
const storeWeapons = (list: Array<GridWeapon>) => {
list.forEach((gridObject: GridWeapon) => {
if (gridObject.mainhand) {
appState.grid.weapons.mainWeapon = gridObject
appState.party.element = gridObject.object.element
} else if (!gridObject.mainhand && gridObject.position != null) {
appState.grid.weapons.allWeapons[gridObject.position] = gridObject
}
})
}
const storeSummons = (list: Array<GridSummon>) => {
list.forEach((gridObject: GridSummon) => {
if (gridObject.main) appState.grid.summons.mainSummon = gridObject
else if (gridObject.friend)
appState.grid.summons.friendSummon = gridObject
else if (
!gridObject.main &&
!gridObject.friend &&
gridObject.position != null
)
appState.grid.summons.allSummons[gridObject.position] = gridObject
})
}
// Methods: Navigating with segmented control
function segmentClicked(event: React.ChangeEvent<HTMLInputElement>) {
const path = [
router.asPath.split('/').filter((el) => el != '')[1],
event.target.value,
].join('/')
switch (event.target.value) {
case 'characters':
router.replace(path)
setCurrentTab(GridType.Character)
break
case 'weapons':
router.replace(path)
setCurrentTab(GridType.Weapon)
break
case 'summons':
router.replace(path)
setCurrentTab(GridType.Summon)
break
default:
break
}
}
// Render: JSX components
const navigation = (
<PartySegmentedControl
selectedTab={currentTab}
onClick={segmentClicked}
onCheckboxChange={checkboxChanged}
/>
)
const weaponGrid = (
<WeaponGrid
new={props.new || false}
editable={editable}
weapons={props.team?.weapons}
createParty={createParty}
pushHistory={props.pushHistory}
/>
)
const summonGrid = (
<SummonGrid
new={props.new || false}
editable={editable}
summons={props.team?.summons}
createParty={createParty}
pushHistory={props.pushHistory}
/>
)
const characterGrid = (
<CharacterGrid
new={props.new || false}
editable={editable}
characters={props.team?.characters}
createParty={createParty}
pushHistory={props.pushHistory}
/>
)
const currentGrid = () => {
switch (currentTab) {
case GridType.Character:
return characterGrid
case GridType.Weapon:
return weaponGrid
case GridType.Summon:
return summonGrid
}
}
return (
<React.Fragment>
{navigation}
<section id="Party">{currentGrid()}</section>
<PartyDetails
party={props.team}
new={props.new || false}
editable={party.editable}
updateCallback={updateDetails}
deleteCallback={deleteTeam}
/>
</React.Fragment>
)
}
Party.defaultProps = defaultProps
export default Party