Merge pull request #157 from jedmund/transcendence
Add support for Transcendence
|
|
@ -57,11 +57,16 @@ const CharacterGrid = (props: Props) => {
|
|||
})
|
||||
const [errorMessage, setErrorMessage] = useState('')
|
||||
|
||||
// Create a temporary state to store previous character uncap values
|
||||
// Create a temporary state to store previous weapon uncap values and transcendence stages
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||
[key: number]: number | undefined
|
||||
}>({})
|
||||
|
||||
const [previousTranscendenceStages, setPreviousTranscendenceStages] =
|
||||
useState<{
|
||||
[key: number]: number | undefined
|
||||
}>({})
|
||||
|
||||
// Set the editable flag only on first load
|
||||
useEffect(() => {
|
||||
// If user is logged in and matches
|
||||
|
|
@ -269,6 +274,7 @@ const CharacterGrid = (props: Props) => {
|
|||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousTranscendenceStage(position)
|
||||
|
||||
try {
|
||||
if (uncapLevel != previousUncapValues[position])
|
||||
|
|
@ -280,11 +286,17 @@ const CharacterGrid = (props: Props) => {
|
|||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateTranscendenceStage(position, previousTranscendenceStages[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
|
||||
let newPreviousUncapValues = { ...previousUncapValues }
|
||||
|
||||
delete newPreviousTranscendenceStages[position]
|
||||
delete newPreviousUncapValues[position]
|
||||
|
||||
setPreviousTranscendenceStages(newPreviousTranscendenceStages)
|
||||
setPreviousUncapValues(newPreviousUncapValues)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,21 +310,25 @@ const CharacterGrid = (props: Props) => {
|
|||
accountState.account.user &&
|
||||
party.user.id === accountState.account.user.id
|
||||
) {
|
||||
memoizeAction(id, position, uncapLevel)
|
||||
memoizeUncapAction(id, position, uncapLevel)
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
|
||||
if (uncapLevel < 6) {
|
||||
updateTranscendenceStage(position, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const memoizeAction = useCallback(
|
||||
const memoizeUncapAction = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
debouncedUncapAction(id, position, uncapLevel)
|
||||
},
|
||||
[props, previousUncapValues]
|
||||
)
|
||||
|
||||
const debouncedAction = useMemo(
|
||||
const debouncedUncapAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
|
|
@ -341,6 +357,106 @@ const CharacterGrid = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
// Methods: Updating transcendence stage
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveTranscendence(
|
||||
id: string,
|
||||
position: number,
|
||||
stage: number
|
||||
) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousTranscendenceStage(position)
|
||||
|
||||
const payload = {
|
||||
character: {
|
||||
uncap_level: stage > 0 ? 6 : 5,
|
||||
transcendence_step: stage,
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
if (stage != previousTranscendenceStages[position])
|
||||
await api.endpoints.grid_characters
|
||||
.update(id, payload)
|
||||
.then((response) => {
|
||||
storeGridCharacter(response.data)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateTranscendenceStage(position, previousTranscendenceStages[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
|
||||
let newPreviousUncapValues = { ...previousUncapValues }
|
||||
|
||||
delete newPreviousTranscendenceStages[position]
|
||||
delete newPreviousUncapValues[position]
|
||||
|
||||
setPreviousTranscendenceStages(newPreviousTranscendenceStages)
|
||||
setPreviousUncapValues(newPreviousUncapValues)
|
||||
}
|
||||
}
|
||||
|
||||
function initiateTranscendenceUpdate(
|
||||
id: string,
|
||||
position: number,
|
||||
stage: number
|
||||
) {
|
||||
if (
|
||||
party.user &&
|
||||
accountState.account.user &&
|
||||
party.user.id === accountState.account.user.id
|
||||
) {
|
||||
memoizeTranscendenceAction(id, position, stage)
|
||||
|
||||
// Optimistically update UI
|
||||
updateTranscendenceStage(position, stage)
|
||||
|
||||
if (stage > 0) {
|
||||
updateUncapLevel(position, 6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const memoizeTranscendenceAction = useCallback(
|
||||
(id: string, position: number, stage: number) => {
|
||||
debouncedTranscendenceAction(id, position, stage)
|
||||
},
|
||||
[props, previousTranscendenceStages]
|
||||
)
|
||||
|
||||
const debouncedTranscendenceAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveTranscendence(id, position, number)
|
||||
}, 500),
|
||||
[props, saveTranscendence]
|
||||
)
|
||||
|
||||
const updateTranscendenceStage = (
|
||||
position: number,
|
||||
stage: number | undefined
|
||||
) => {
|
||||
const character = appState.grid.characters[position]
|
||||
if (character && stage !== undefined) {
|
||||
character.transcendence_step = stage
|
||||
appState.grid.characters[position] = character
|
||||
}
|
||||
}
|
||||
|
||||
function storePreviousTranscendenceStage(position: number) {
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
|
||||
if (grid.characters[position]) {
|
||||
newPreviousValues[position] = grid.characters[position]?.uncap_level
|
||||
setPreviousTranscendenceStages(newPreviousValues)
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAlert() {
|
||||
setErrorMessage('')
|
||||
}
|
||||
|
|
@ -380,6 +496,7 @@ const CharacterGrid = (props: Props) => {
|
|||
position={i}
|
||||
updateObject={receiveCharacterFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
updateTranscendence={initiateTranscendenceUpdate}
|
||||
removeCharacter={removeCharacter}
|
||||
/>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ interface Props {
|
|||
removeCharacter: (id: string) => void
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
updateTranscendence: (id: string, position: number, stage: number) => void
|
||||
}
|
||||
|
||||
const CharacterUnit = ({
|
||||
|
|
@ -49,6 +50,7 @@ const CharacterUnit = ({
|
|||
removeCharacter: sendCharacterToRemove,
|
||||
updateObject,
|
||||
updateUncap,
|
||||
updateTranscendence,
|
||||
}: Props) => {
|
||||
// Translations and locale
|
||||
const { t } = useTranslation('common')
|
||||
|
|
@ -156,6 +158,10 @@ const CharacterUnit = ({
|
|||
if (gridCharacter) updateUncap(gridCharacter.id, position, uncap)
|
||||
}
|
||||
|
||||
function passTranscendenceData(stage: number) {
|
||||
if (gridCharacter) updateTranscendence(gridCharacter.id, position, stage)
|
||||
}
|
||||
|
||||
function removeCharacter() {
|
||||
if (gridCharacter) sendCharacterToRemove(gridCharacter.id)
|
||||
}
|
||||
|
|
@ -169,8 +175,8 @@ const CharacterUnit = ({
|
|||
|
||||
// Change the image based on the uncap level
|
||||
let suffix = '01'
|
||||
if (gridCharacter.uncap_level == 6) suffix = '04'
|
||||
else if (gridCharacter.uncap_level == 5) suffix = '03'
|
||||
if (gridCharacter.transcendence_step > 0) suffix = '04'
|
||||
else if (gridCharacter.uncap_level >= 5) suffix = '03'
|
||||
else if (gridCharacter.uncap_level > 2) suffix = '02'
|
||||
|
||||
// Special casing for Lyria (and Young Cat eventually)
|
||||
|
|
@ -280,7 +286,11 @@ const CharacterUnit = ({
|
|||
}
|
||||
|
||||
const image = (
|
||||
<div className="CharacterImage" onClick={openSearchModal}>
|
||||
<div
|
||||
className="CharacterImage"
|
||||
onClick={openSearchModal}
|
||||
tabIndex={gridCharacter ? gridCharacter.position * 7 : 0}
|
||||
>
|
||||
<img
|
||||
alt={character?.name[locale]}
|
||||
className="grid_image"
|
||||
|
|
@ -308,7 +318,11 @@ const CharacterUnit = ({
|
|||
flb={character.uncap.flb || false}
|
||||
ulb={character.uncap.ulb || false}
|
||||
uncapLevel={gridCharacter.uncap_level}
|
||||
transcendenceStage={gridCharacter.transcendence_step}
|
||||
position={gridCharacter.position}
|
||||
editable={editable}
|
||||
updateUncap={passUncapData}
|
||||
updateTranscendence={passTranscendenceData}
|
||||
special={character.special}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ interface Props {
|
|||
removeSummon: (id: string) => void
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
updateTranscendence: (id: string, position: number, stage: number) => void
|
||||
}
|
||||
|
||||
const ExtraSummons = (props: Props) => {
|
||||
|
|
@ -36,6 +37,7 @@ const ExtraSummons = (props: Props) => {
|
|||
gridSummon={props.grid[props.offset + i]}
|
||||
updateObject={props.updateObject}
|
||||
updateUncap={props.updateUncap}
|
||||
updateTranscendence={props.updateTranscendence}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
|
|
|
|||
9
components/Popover/index.scss
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
.Popover {
|
||||
animation: scaleIn $duration-zoom ease-out;
|
||||
background: var(--dialog-bg);
|
||||
border-radius: $card-corner;
|
||||
border: 0.5px solid rgba(0, 0, 0, 0.18);
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.24);
|
||||
transform-origin: var(--radix-popover-content-transform-origin);
|
||||
outline: none;
|
||||
}
|
||||
37
components/Popover/index.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React, { PropsWithChildren } from 'react'
|
||||
import classnames from 'classnames'
|
||||
import * as PopoverPrimitive from '@radix-ui/react-popover'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {}
|
||||
|
||||
export const Popover = PopoverPrimitive.Root
|
||||
export const PopoverAnchor = PopoverPrimitive.Anchor
|
||||
export const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
export const PopoverContent = React.forwardRef<HTMLDivElement, Props>(
|
||||
({ children, ...props }: PropsWithChildren<Props>, forwardedRef) => {
|
||||
const classes = classnames(props.className, {
|
||||
Popover: true,
|
||||
})
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
sideOffset={5}
|
||||
{...props}
|
||||
className={classes}
|
||||
ref={forwardedRef}
|
||||
>
|
||||
{children}
|
||||
<PopoverPrimitive.Arrow />
|
||||
</PopoverPrimitive.Content>
|
||||
</PopoverPrimitive.Portal>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
@ -42,10 +42,14 @@ const SummonGrid = (props: Props) => {
|
|||
const { party, grid } = useSnapshot(appState)
|
||||
const [slug, setSlug] = useState()
|
||||
|
||||
// Create a temporary state to store previous weapon uncap value
|
||||
// Create a temporary state to store previous weapon uncap values and transcendence stages
|
||||
const [previousUncapValues, setPreviousUncapValues] = useState<{
|
||||
[key: number]: number
|
||||
}>({})
|
||||
const [previousTranscendenceStages, setPreviousTranscendenceStages] =
|
||||
useState<{
|
||||
[key: number]: number
|
||||
}>({})
|
||||
|
||||
// Set the editable flag only on first load
|
||||
useEffect(() => {
|
||||
|
|
@ -155,6 +159,7 @@ const SummonGrid = (props: Props) => {
|
|||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveUncap(id: string, position: number, uncapLevel: number) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousTranscendenceStage(position)
|
||||
|
||||
try {
|
||||
if (uncapLevel != previousUncapValues[position])
|
||||
|
|
@ -166,11 +171,17 @@ const SummonGrid = (props: Props) => {
|
|||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateTranscendenceStage(position, previousTranscendenceStages[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
delete newPreviousValues[position]
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
|
||||
let newPreviousUncapValues = { ...previousUncapValues }
|
||||
|
||||
delete newPreviousTranscendenceStages[position]
|
||||
delete newPreviousUncapValues[position]
|
||||
|
||||
setPreviousTranscendenceStages(newPreviousTranscendenceStages)
|
||||
setPreviousUncapValues(newPreviousUncapValues)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,21 +195,25 @@ const SummonGrid = (props: Props) => {
|
|||
accountState.account.user &&
|
||||
party.user.id === accountState.account.user.id
|
||||
) {
|
||||
memoizeAction(id, position, uncapLevel)
|
||||
memoizeUncapAction(id, position, uncapLevel)
|
||||
|
||||
// Optimistically update UI
|
||||
updateUncapLevel(position, uncapLevel)
|
||||
|
||||
if (uncapLevel < 6) {
|
||||
updateTranscendenceStage(position, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const memoizeAction = useCallback(
|
||||
const memoizeUncapAction = useCallback(
|
||||
(id: string, position: number, uncapLevel: number) => {
|
||||
debouncedAction(id, position, uncapLevel)
|
||||
debouncedUncapAction(id, position, uncapLevel)
|
||||
},
|
||||
[props, previousUncapValues]
|
||||
)
|
||||
|
||||
const debouncedAction = useMemo(
|
||||
const debouncedUncapAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveUncap(id, position, number)
|
||||
|
|
@ -237,6 +252,116 @@ const SummonGrid = (props: Props) => {
|
|||
setPreviousUncapValues(newPreviousValues)
|
||||
}
|
||||
|
||||
// Methods: Updating transcendence stage
|
||||
// Note: Saves, but debouncing is not working properly
|
||||
async function saveTranscendence(
|
||||
id: string,
|
||||
position: number,
|
||||
stage: number
|
||||
) {
|
||||
storePreviousUncapValue(position)
|
||||
storePreviousTranscendenceStage(position)
|
||||
|
||||
const payload = {
|
||||
summon: {
|
||||
uncap_level: stage > 0 ? 6 : 5,
|
||||
transcendence_step: stage,
|
||||
},
|
||||
}
|
||||
|
||||
try {
|
||||
if (stage != previousTranscendenceStages[position])
|
||||
await api.endpoints.grid_summons
|
||||
.update(id, payload)
|
||||
.then((response) => {
|
||||
storeGridSummon(response.data.grid_summon)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
// Revert optimistic UI
|
||||
updateUncapLevel(position, previousUncapValues[position])
|
||||
updateTranscendenceStage(position, previousTranscendenceStages[position])
|
||||
|
||||
// Remove optimistic key
|
||||
let newPreviousTranscendenceStages = { ...previousTranscendenceStages }
|
||||
let newPreviousUncapValues = { ...previousUncapValues }
|
||||
|
||||
delete newPreviousTranscendenceStages[position]
|
||||
delete newPreviousUncapValues[position]
|
||||
|
||||
setPreviousTranscendenceStages(newPreviousTranscendenceStages)
|
||||
setPreviousUncapValues(newPreviousUncapValues)
|
||||
}
|
||||
}
|
||||
|
||||
function initiateTranscendenceUpdate(
|
||||
id: string,
|
||||
position: number,
|
||||
stage: number
|
||||
) {
|
||||
if (
|
||||
party.user &&
|
||||
accountState.account.user &&
|
||||
party.user.id === accountState.account.user.id
|
||||
) {
|
||||
memoizeTranscendenceAction(id, position, stage)
|
||||
|
||||
// Optimistically update UI
|
||||
updateTranscendenceStage(position, stage)
|
||||
|
||||
if (stage > 0) {
|
||||
updateUncapLevel(position, 6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const memoizeTranscendenceAction = useCallback(
|
||||
(id: string, position: number, stage: number) => {
|
||||
debouncedTranscendenceAction(id, position, stage)
|
||||
},
|
||||
[props, previousTranscendenceStages]
|
||||
)
|
||||
|
||||
const debouncedTranscendenceAction = useMemo(
|
||||
() =>
|
||||
debounce((id, position, number) => {
|
||||
saveTranscendence(id, position, number)
|
||||
}, 500),
|
||||
[props, saveTranscendence]
|
||||
)
|
||||
|
||||
const updateTranscendenceStage = (position: number, stage: number) => {
|
||||
if (appState.grid.summons.mainSummon && position == -1)
|
||||
appState.grid.summons.mainSummon.transcendence_step = stage
|
||||
else if (appState.grid.summons.friendSummon && position == 6)
|
||||
appState.grid.summons.friendSummon.transcendence_step = stage
|
||||
else {
|
||||
const summon = appState.grid.summons.allSummons[position]
|
||||
if (summon) {
|
||||
summon.transcendence_step = stage
|
||||
appState.grid.summons.allSummons[position] = summon
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function storePreviousTranscendenceStage(position: number) {
|
||||
// Save the current value in case of an unexpected result
|
||||
let newPreviousValues = { ...previousUncapValues }
|
||||
|
||||
if (appState.grid.summons.mainSummon && position == -1)
|
||||
newPreviousValues[position] = appState.grid.summons.mainSummon.uncap_level
|
||||
else if (appState.grid.summons.friendSummon && position == 6)
|
||||
newPreviousValues[position] =
|
||||
appState.grid.summons.friendSummon.uncap_level
|
||||
else {
|
||||
const summon = appState.grid.summons.allSummons[position]
|
||||
newPreviousValues[position] = summon ? summon.uncap_level : 0
|
||||
}
|
||||
|
||||
setPreviousUncapValues(newPreviousValues)
|
||||
}
|
||||
|
||||
async function removeSummon(id: string) {
|
||||
try {
|
||||
const response = await api.endpoints.grid_summons.destroy({ id: id })
|
||||
|
|
@ -267,6 +392,7 @@ const SummonGrid = (props: Props) => {
|
|||
removeSummon={removeSummon}
|
||||
updateObject={receiveSummonFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
updateTranscendence={initiateTranscendenceUpdate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
|
@ -280,11 +406,14 @@ const SummonGrid = (props: Props) => {
|
|||
key="grid_friend_summon"
|
||||
position={6}
|
||||
unitType={2}
|
||||
removeSummon={removeSummon}
|
||||
updateObject={receiveSummonFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
updateTranscendence={initiateTranscendenceUpdate}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
const summonGridElement = (
|
||||
<div id="LabeledGrid">
|
||||
<div className="Label">{t('summons.summons')}</div>
|
||||
|
|
@ -300,6 +429,7 @@ const SummonGrid = (props: Props) => {
|
|||
removeSummon={removeSummon}
|
||||
updateObject={receiveSummonFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
updateTranscendence={initiateTranscendenceUpdate}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
|
|
@ -307,6 +437,7 @@ const SummonGrid = (props: Props) => {
|
|||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
const subAuraSummonElement = (
|
||||
<ExtraSummons
|
||||
grid={grid.summons.allSummons}
|
||||
|
|
@ -316,8 +447,10 @@ const SummonGrid = (props: Props) => {
|
|||
removeSummon={removeSummon}
|
||||
updateObject={receiveSummonFromSearch}
|
||||
updateUncap={initiateUncapUpdate}
|
||||
updateTranscendence={initiateTranscendenceUpdate}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div id="SummonGrid">
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ interface Props {
|
|||
removeSummon: (id: string) => void
|
||||
updateObject: (object: SearchableObject, position: number) => void
|
||||
updateUncap: (id: string, position: number, uncap: number) => void
|
||||
updateTranscendence: (id: string, position: number, stage: number) => void
|
||||
}
|
||||
|
||||
const SummonUnit = ({
|
||||
|
|
@ -39,6 +40,7 @@ const SummonUnit = ({
|
|||
removeSummon: sendSummonToRemove,
|
||||
updateObject,
|
||||
updateUncap,
|
||||
updateTranscendence,
|
||||
}: Props) => {
|
||||
// Translations and locale
|
||||
const { t } = useTranslation('common')
|
||||
|
|
@ -105,6 +107,10 @@ const SummonUnit = ({
|
|||
if (gridSummon) updateUncap(gridSummon.id, position, uncap)
|
||||
}
|
||||
|
||||
function passTranscendenceData(stage: number) {
|
||||
if (gridSummon) updateTranscendence(gridSummon.id, position, stage)
|
||||
}
|
||||
|
||||
function removeSummon() {
|
||||
if (gridSummon) sendSummonToRemove(gridSummon.id)
|
||||
}
|
||||
|
|
@ -133,11 +139,14 @@ const SummonUnit = ({
|
|||
]
|
||||
|
||||
let suffix = ''
|
||||
if (
|
||||
if (gridSummon.object.uncap.xlb && gridSummon.uncap_level == 6) {
|
||||
suffix = '_03'
|
||||
} else if (
|
||||
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||
gridSummon.uncap_level == 5
|
||||
)
|
||||
) {
|
||||
suffix = '_02'
|
||||
}
|
||||
|
||||
// Generate the correct source for the summon
|
||||
if (unitType == 0 || unitType == 2)
|
||||
|
|
@ -243,8 +252,13 @@ const SummonUnit = ({
|
|||
type="summon"
|
||||
ulb={gridSummon.object.uncap.ulb || false}
|
||||
flb={gridSummon.object.uncap.flb || false}
|
||||
xlb={gridSummon.object.uncap.xlb || false}
|
||||
editable={editable}
|
||||
uncapLevel={gridSummon.uncap_level}
|
||||
transcendenceStage={gridSummon.transcendence_step}
|
||||
position={gridSummon.position}
|
||||
updateUncap={passUncapData}
|
||||
updateTranscendence={passTranscendenceData}
|
||||
special={false}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
|||
83
components/TranscendenceFragment/index.scss
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
.Fragment {
|
||||
$degrees: 72deg;
|
||||
|
||||
$origWidth: 29px;
|
||||
$origHeight: 54px;
|
||||
|
||||
$scaledWidth: 12px;
|
||||
$scaledHeight: calc(($scaledWidth / $origWidth) * $origHeight);
|
||||
|
||||
$scale: 1.2;
|
||||
|
||||
@include hidpiImage(
|
||||
'/icons/transcendence/interactive/interactive-piece',
|
||||
png,
|
||||
$scaledWidth,
|
||||
$scaledHeight
|
||||
);
|
||||
|
||||
position: absolute;
|
||||
z-index: 32;
|
||||
|
||||
aspect-ratio: 29 / 54;
|
||||
height: $scaledHeight;
|
||||
width: $scaledWidth;
|
||||
opacity: 0;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.Visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.Stage1 {
|
||||
top: 3px;
|
||||
left: 18px;
|
||||
|
||||
// &:hover {
|
||||
// transform: scale($scale, $scale) translateY(-2px);
|
||||
// }
|
||||
}
|
||||
|
||||
&.Stage2 {
|
||||
top: 10px;
|
||||
left: 27px;
|
||||
transform: rotate($degrees);
|
||||
|
||||
// &:hover {
|
||||
// transform: rotate($degrees) scale($scale, $scale) translateY(-2px);
|
||||
// }
|
||||
}
|
||||
|
||||
&.Stage3 {
|
||||
top: 21px;
|
||||
left: 24px;
|
||||
transform: rotate($degrees * 2);
|
||||
|
||||
// &:hover {
|
||||
// transform: rotate($degrees * 2) scale($scale, $scale) translateY(-1px);
|
||||
// }
|
||||
}
|
||||
|
||||
&.Stage4 {
|
||||
top: 21px;
|
||||
left: 12px;
|
||||
transform: rotate($degrees * 3);
|
||||
|
||||
// &:hover {
|
||||
// transform: rotate($degrees * 3) scale($scale, $scale) translateY(-1px);
|
||||
// }
|
||||
}
|
||||
|
||||
&.Stage5 {
|
||||
top: 10px;
|
||||
left: 8px;
|
||||
transform: rotate($degrees * 4);
|
||||
|
||||
// &:hover {
|
||||
// transform: rotate($degrees * 4) scale($scale, $scale) translateY(-1px);
|
||||
// }
|
||||
}
|
||||
}
|
||||
49
components/TranscendenceFragment/index.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import React from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
stage: number
|
||||
interactive: boolean
|
||||
visible: boolean
|
||||
onClick?: (index: number) => void
|
||||
onHover?: (index: number) => void
|
||||
}
|
||||
|
||||
const TranscendenceFragment = ({
|
||||
interactive,
|
||||
stage,
|
||||
visible,
|
||||
onClick,
|
||||
onHover,
|
||||
}: Props) => {
|
||||
const classes = classnames({
|
||||
Fragment: true,
|
||||
Visible: visible,
|
||||
Stage1: stage === 1,
|
||||
Stage2: stage === 2,
|
||||
Stage3: stage === 3,
|
||||
Stage4: stage === 4,
|
||||
Stage5: stage === 5,
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
if (interactive && onClick) onClick(stage)
|
||||
}
|
||||
|
||||
function handleHover() {
|
||||
if (interactive && onHover) onHover(stage)
|
||||
}
|
||||
|
||||
return (
|
||||
<i className={classes} onClick={handleClick} onMouseOver={handleHover} />
|
||||
)
|
||||
}
|
||||
|
||||
TranscendenceFragment.defaultProps = {
|
||||
interactive: false,
|
||||
visible: false,
|
||||
}
|
||||
|
||||
export default TranscendenceFragment
|
||||
25
components/TranscendencePopover/index.scss
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
.Transcendence.Popover {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: $unit-half;
|
||||
display: flex;
|
||||
width: $unit-10x;
|
||||
height: $unit-10x;
|
||||
padding: $unit;
|
||||
justify-content: center;
|
||||
z-index: 32;
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-small;
|
||||
font-weight: $medium;
|
||||
}
|
||||
|
||||
.Pending {
|
||||
color: $yellow;
|
||||
}
|
||||
}
|
||||
87
components/TranscendencePopover/index.tsx
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import React, { PropsWithChildren, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import { Popover, PopoverAnchor, PopoverContent } from '~components/Popover'
|
||||
import TranscendenceStar from '~components/TranscendenceStar'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {
|
||||
open: boolean
|
||||
stage: number
|
||||
onOpenChange?: (open: boolean) => void
|
||||
sendValue?: (stage: number) => void
|
||||
}
|
||||
|
||||
const TranscendencePopover = ({
|
||||
children,
|
||||
open: popoverOpen,
|
||||
stage,
|
||||
tabIndex,
|
||||
onOpenChange,
|
||||
sendValue,
|
||||
}: PropsWithChildren<Props>) => {
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const [currentStage, setCurrentStage] = useState(0)
|
||||
|
||||
const popoverRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
const classes = classNames({
|
||||
Transcendence: true,
|
||||
})
|
||||
|
||||
const levelClasses = classNames({
|
||||
Pending: stage != currentStage,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (open) popoverRef.current?.focus()
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentStage(stage)
|
||||
}, [stage])
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(popoverOpen)
|
||||
}, [popoverOpen])
|
||||
|
||||
function handleFragmentClicked(newStage: number) {
|
||||
setCurrentStage(newStage)
|
||||
if (sendValue) sendValue(newStage)
|
||||
}
|
||||
|
||||
function handleFragmentHovered(newStage: number) {
|
||||
setCurrentStage(newStage)
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<PopoverAnchor>{children}</PopoverAnchor>
|
||||
<PopoverContent className={classes} ref={popoverRef} tabIndex={tabIndex}>
|
||||
<TranscendenceStar
|
||||
className="Interactive Base"
|
||||
editable={true}
|
||||
interactive={true}
|
||||
stage={stage}
|
||||
onFragmentClick={handleFragmentClicked}
|
||||
onFragmentHover={handleFragmentHovered}
|
||||
/>
|
||||
<h4>
|
||||
<span>{t('level')} </span>
|
||||
<span className={levelClasses}>{200 + 10 * currentStage}</span>
|
||||
</h4>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
export default TranscendencePopover
|
||||
108
components/TranscendenceStar/index.scss
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
.TranscendenceStar {
|
||||
$size: 18px;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
&.Immutable {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.Empty {
|
||||
@include hidpiImage('/icons/transcendence/0/stage-0', png, $size, $size);
|
||||
}
|
||||
|
||||
&.Stage1 {
|
||||
@include hidpiImage('/icons/transcendence/1/stage-1', png, $size, $size);
|
||||
}
|
||||
|
||||
&.Stage2 {
|
||||
@include hidpiImage('/icons/transcendence/2/stage-2', png, $size, $size);
|
||||
}
|
||||
|
||||
&.Stage3 {
|
||||
@include hidpiImage('/icons/transcendence/3/stage-3', png, $size, $size);
|
||||
}
|
||||
|
||||
&.Stage4 {
|
||||
@include hidpiImage('/icons/transcendence/4/stage-4', png, $size, $size);
|
||||
}
|
||||
|
||||
&.Stage5 {
|
||||
@include hidpiImage('/icons/transcendence/5/stage-5', png, $size, $size);
|
||||
}
|
||||
|
||||
.Figure {
|
||||
$size: 18px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 54px 54px;
|
||||
display: block;
|
||||
height: $size;
|
||||
width: $size;
|
||||
|
||||
&.Interactive.Base {
|
||||
$size: $unit-6x;
|
||||
|
||||
@include hidpiImage(
|
||||
'/icons/transcendence/interactive/interactive-base',
|
||||
png,
|
||||
$size,
|
||||
$size
|
||||
);
|
||||
|
||||
height: $size;
|
||||
width: $size;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
&.Stage1 {
|
||||
background-image: url('/icons/transcendence/1/step-1@3x.png');
|
||||
|
||||
&:hover {
|
||||
background-image: url('/icons/transcendence/1/step-1-hover@3x.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.Stage2 {
|
||||
background-image: url('/icons/transcendence/2/step-2@3x.png');
|
||||
|
||||
&:hover {
|
||||
background-image: url('/icons/transcendence/2/step-2-hover@3x.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.Stage3 {
|
||||
background-image: url('/icons/transcendence/3/step-3@3x.png');
|
||||
|
||||
&:hover {
|
||||
background-image: url('/icons/transcendence/3/step-3-hover@3x.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.Stage4 {
|
||||
background-image: url('/icons/transcendence/4/step-4@3x.png');
|
||||
|
||||
&:hover {
|
||||
background-image: url('/icons/transcendence/4/step-4-hover@3x.png');
|
||||
}
|
||||
}
|
||||
|
||||
&.Stage5 {
|
||||
background-image: url('/icons/transcendence/5/step-5@3x.png');
|
||||
|
||||
&:hover {
|
||||
background-image: url('/icons/transcendence/5/step-5-hover@3x.png');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
components/TranscendenceStar/index.tsx
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import classnames from 'classnames'
|
||||
|
||||
import TranscendenceFragment from '~components/TranscendenceFragment'
|
||||
import './index.scss'
|
||||
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {
|
||||
className?: string
|
||||
stage: number
|
||||
editable: boolean
|
||||
interactive: boolean
|
||||
onStarClick?: () => void
|
||||
onFragmentClick?: (newStage: number) => void
|
||||
onFragmentHover?: (newStage: number) => void
|
||||
}
|
||||
|
||||
const NUM_FRAGMENTS = 5
|
||||
|
||||
const TranscendenceStar = ({
|
||||
className,
|
||||
interactive,
|
||||
stage,
|
||||
editable,
|
||||
tabIndex,
|
||||
onStarClick,
|
||||
onFragmentClick,
|
||||
onFragmentHover,
|
||||
}: Props) => {
|
||||
const [visibleStage, setVisibleStage] = useState(0)
|
||||
const [currentStage, setCurrentStage] = useState(0)
|
||||
const [immutable, setImmutable] = useState(false)
|
||||
|
||||
// Classes
|
||||
const starClasses = classnames({
|
||||
TranscendenceStar: true,
|
||||
Immutable: immutable,
|
||||
Empty: stage === 0,
|
||||
Stage1: stage === 1,
|
||||
Stage2: stage === 2,
|
||||
Stage3: stage === 3,
|
||||
Stage4: stage === 4,
|
||||
Stage5: stage === 5,
|
||||
})
|
||||
|
||||
const baseImageClasses = classnames(className, {
|
||||
Figure: true,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleStage(stage)
|
||||
setCurrentStage(stage)
|
||||
}, [stage])
|
||||
|
||||
function handleClick() {
|
||||
if (onStarClick) {
|
||||
onStarClick()
|
||||
}
|
||||
}
|
||||
|
||||
function handleFragmentClick(index: number) {
|
||||
let newStage = index
|
||||
if (index === currentStage) newStage = 0
|
||||
|
||||
setVisibleStage(newStage)
|
||||
setCurrentStage(newStage)
|
||||
if (onFragmentClick) onFragmentClick(newStage)
|
||||
}
|
||||
|
||||
function handleFragmentHover(index: number) {
|
||||
setVisibleStage(index)
|
||||
if (onFragmentHover) onFragmentHover(index)
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
setVisibleStage(currentStage)
|
||||
if (onFragmentHover) onFragmentHover(currentStage)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={starClasses}
|
||||
onClick={editable ? handleClick : () => {}}
|
||||
onMouseLeave={interactive ? handleMouseLeave : () => {}}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<div className="Fragments">
|
||||
{[...Array(NUM_FRAGMENTS)].map((e, i) => {
|
||||
const loopStage = i + 1
|
||||
return interactive ? (
|
||||
<TranscendenceFragment
|
||||
key={`fragment_${loopStage}`}
|
||||
stage={loopStage}
|
||||
visible={loopStage <= visibleStage}
|
||||
interactive={interactive}
|
||||
onClick={handleFragmentClick}
|
||||
onHover={handleFragmentHover}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
<i className={baseImageClasses} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
TranscendenceStar.defaultProps = {
|
||||
stage: 0,
|
||||
editable: false,
|
||||
interactive: false,
|
||||
}
|
||||
|
||||
export default TranscendenceStar
|
||||
|
|
@ -1,3 +1,7 @@
|
|||
.Uncap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.UncapIndicator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import UncapStar from '~components/UncapStar'
|
||||
import TranscendencePopover from '~components/TranscendencePopover'
|
||||
import TranscendenceStar from '~components/TranscendenceStar'
|
||||
|
||||
import './index.scss'
|
||||
|
||||
|
|
@ -7,14 +9,22 @@ interface Props {
|
|||
type: 'character' | 'weapon' | 'summon'
|
||||
rarity?: number
|
||||
uncapLevel?: number
|
||||
position: number
|
||||
transcendenceStage?: number
|
||||
editable: boolean
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
xlb?: boolean
|
||||
special: boolean
|
||||
updateUncap?: (index: number) => void
|
||||
updateTranscendence?: (index: number) => void
|
||||
}
|
||||
|
||||
const UncapIndicator = (props: Props) => {
|
||||
const numStars = setNumStars()
|
||||
|
||||
const [popoverOpen, setPopoverOpen] = useState(false)
|
||||
|
||||
function setNumStars() {
|
||||
let numStars
|
||||
|
||||
|
|
@ -37,7 +47,9 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (props.ulb) {
|
||||
if (props.xlb) {
|
||||
numStars = 6
|
||||
} else if (props.ulb) {
|
||||
numStars = 5
|
||||
} else if (props.flb) {
|
||||
numStars = 4
|
||||
|
|
@ -56,14 +68,41 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
function togglePopover(open: boolean) {
|
||||
setPopoverOpen(open)
|
||||
}
|
||||
|
||||
function sendTranscendenceStage(stage: number) {
|
||||
if (props.updateTranscendence) props.updateTranscendence(stage)
|
||||
togglePopover(false)
|
||||
}
|
||||
|
||||
const transcendence = (i: number) => {
|
||||
return (
|
||||
<UncapStar
|
||||
ulb={true}
|
||||
empty={props.uncapLevel ? i >= props.uncapLevel : false}
|
||||
const tabIndex = props.position * 7 + i + 1
|
||||
return props.type === 'character' || props.type === 'summon' ? (
|
||||
<TranscendencePopover
|
||||
open={popoverOpen}
|
||||
stage={props.transcendenceStage ? props.transcendenceStage : 0}
|
||||
onOpenChange={togglePopover}
|
||||
sendValue={sendTranscendenceStage}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<TranscendenceStar
|
||||
key={`star_${i}`}
|
||||
stage={props.transcendenceStage}
|
||||
editable={props.editable}
|
||||
interactive={false}
|
||||
onStarClick={() => togglePopover(true)}
|
||||
/>
|
||||
</TranscendencePopover>
|
||||
) : (
|
||||
<TranscendenceStar
|
||||
key={`star_${i}`}
|
||||
stage={props.transcendenceStage}
|
||||
editable={props.editable}
|
||||
interactive={false}
|
||||
tabIndex={tabIndex}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -76,7 +115,8 @@ const UncapIndicator = (props: Props) => {
|
|||
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
onStarClick={toggleStar}
|
||||
tabIndex={props.position * 7 + i + 1}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -89,7 +129,8 @@ const UncapIndicator = (props: Props) => {
|
|||
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
onStarClick={toggleStar}
|
||||
tabIndex={props.position * 7 + i + 1}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
@ -101,29 +142,38 @@ const UncapIndicator = (props: Props) => {
|
|||
empty={props.uncapLevel != null ? i >= props.uncapLevel : false}
|
||||
key={`star_${i}`}
|
||||
index={i}
|
||||
onClick={toggleStar}
|
||||
onStarClick={toggleStar}
|
||||
tabIndex={props.position * 7 + i + 1}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="UncapIndicator">
|
||||
{Array.from(Array(numStars)).map((x, i) => {
|
||||
if (props.type === 'character' && i > 4) {
|
||||
if (props.special) return ulb(i)
|
||||
else return transcendence(i)
|
||||
} else if (
|
||||
(props.special && props.type === 'character' && i == 3) ||
|
||||
(props.type === 'character' && i == 4) ||
|
||||
(props.type !== 'character' && i > 2)
|
||||
) {
|
||||
return flb(i)
|
||||
} else {
|
||||
return mlb(i)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
<div className="Uncap">
|
||||
<ul className="UncapIndicator">
|
||||
{Array.from(Array(numStars)).map((x, i) => {
|
||||
if (props.type === 'character' && i > 4) {
|
||||
if (props.special) return ulb(i)
|
||||
else return transcendence(i)
|
||||
} else if (props.type === 'summon' && i > 4) {
|
||||
return transcendence(i)
|
||||
} else if (
|
||||
(props.special && props.type === 'character' && i == 3) ||
|
||||
(props.type === 'character' && i == 4) ||
|
||||
(props.type !== 'character' && i > 2)
|
||||
) {
|
||||
return flb(i)
|
||||
} else {
|
||||
return mlb(i)
|
||||
}
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
UncapIndicator.defaultProps = {
|
||||
editable: false,
|
||||
}
|
||||
|
||||
export default UncapIndicator
|
||||
|
|
|
|||
|
|
@ -3,13 +3,17 @@ import classnames from 'classnames'
|
|||
|
||||
import './index.scss'
|
||||
|
||||
interface Props {
|
||||
interface Props
|
||||
extends React.DetailedHTMLProps<
|
||||
React.DialogHTMLAttributes<HTMLDivElement>,
|
||||
HTMLDivElement
|
||||
> {
|
||||
empty: boolean
|
||||
special: boolean
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
index: number
|
||||
onClick: (index: number, empty: boolean) => void
|
||||
onStarClick: (index: number, empty: boolean) => void
|
||||
}
|
||||
|
||||
const UncapStar = (props: Props) => {
|
||||
|
|
@ -23,10 +27,12 @@ const UncapStar = (props: Props) => {
|
|||
})
|
||||
|
||||
function clicked() {
|
||||
props.onClick(props.index, props.empty)
|
||||
props.onStarClick(props.index, props.empty)
|
||||
}
|
||||
|
||||
return <li className={classes} onClick={clicked}></li>
|
||||
return (
|
||||
<li className={classes} tabIndex={props.tabIndex} onClick={clicked}></li>
|
||||
)
|
||||
}
|
||||
|
||||
UncapStar.defaultProps = {
|
||||
|
|
|
|||
|
|
@ -548,6 +548,7 @@ const WeaponUnit = ({
|
|||
ulb={gridWeapon.object.uncap.ulb || false}
|
||||
flb={gridWeapon.object.uncap.flb || false}
|
||||
uncapLevel={gridWeapon.uncap_level}
|
||||
position={gridWeapon.position}
|
||||
updateUncap={passUncapData}
|
||||
special={false}
|
||||
/>
|
||||
|
|
|
|||
93
package-lock.json
generated
|
|
@ -10,6 +10,7 @@
|
|||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.1",
|
||||
"@radix-ui/react-hover-card": "^1.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-select": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.0.1",
|
||||
"@radix-ui/react-toast": "^1.1.2",
|
||||
|
|
@ -2325,6 +2326,55 @@
|
|||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.3.tgz",
|
||||
"integrity": "sha512-YwedSukfWsyJs3/yP3yXUq44k4/JBe3jqU63Z8v2i19qZZ3dsx32oma17ztgclWPNuqp3A+Xa9UiDlZHyVX8Vg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.2",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-popper": "1.1.0",
|
||||
"@radix-ui/react-portal": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-slot": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.0.tgz",
|
||||
"integrity": "sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@floating-ui/react-dom": "0.7.2",
|
||||
"@radix-ui/react-arrow": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0",
|
||||
"@radix-ui/react-use-rect": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0",
|
||||
"@radix-ui/rect": "1.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.8 || ^17.0 || ^18.0",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-popper": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.1.tgz",
|
||||
|
|
@ -8809,6 +8859,49 @@
|
|||
"react-remove-scroll": "2.5.5"
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popover": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.3.tgz",
|
||||
"integrity": "sha512-YwedSukfWsyJs3/yP3yXUq44k4/JBe3jqU63Z8v2i19qZZ3dsx32oma17ztgclWPNuqp3A+Xa9UiDlZHyVX8Vg==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@radix-ui/primitive": "1.0.0",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-dismissable-layer": "1.0.2",
|
||||
"@radix-ui/react-focus-guards": "1.0.0",
|
||||
"@radix-ui/react-focus-scope": "1.0.1",
|
||||
"@radix-ui/react-id": "1.0.0",
|
||||
"@radix-ui/react-popper": "1.1.0",
|
||||
"@radix-ui/react-portal": "1.0.1",
|
||||
"@radix-ui/react-presence": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-slot": "1.0.1",
|
||||
"@radix-ui/react-use-controllable-state": "1.0.0",
|
||||
"aria-hidden": "^1.1.1",
|
||||
"react-remove-scroll": "2.5.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-popper": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.0.tgz",
|
||||
"integrity": "sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.13.10",
|
||||
"@floating-ui/react-dom": "0.7.2",
|
||||
"@radix-ui/react-arrow": "1.0.1",
|
||||
"@radix-ui/react-compose-refs": "1.0.0",
|
||||
"@radix-ui/react-context": "1.0.0",
|
||||
"@radix-ui/react-primitive": "1.0.1",
|
||||
"@radix-ui/react-use-callback-ref": "1.0.0",
|
||||
"@radix-ui/react-use-layout-effect": "1.0.0",
|
||||
"@radix-ui/react-use-rect": "1.0.0",
|
||||
"@radix-ui/react-use-size": "1.0.0",
|
||||
"@radix-ui/rect": "1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@radix-ui/react-popper": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
"@radix-ui/react-dialog": "^1.0.2",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.1",
|
||||
"@radix-ui/react-hover-card": "^1.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.3",
|
||||
"@radix-ui/react-select": "^1.1.2",
|
||||
"@radix-ui/react-switch": "^1.0.1",
|
||||
"@radix-ui/react-toast": "^1.1.2",
|
||||
|
|
|
|||
BIN
public/icons/transcendence/0/stage-0.png
Normal file
|
After Width: | Height: | Size: 770 B |
BIN
public/icons/transcendence/0/stage-0@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/icons/transcendence/0/stage-0@3x.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/icons/transcendence/1/stage-1-hover.png
Normal file
|
After Width: | Height: | Size: 755 B |
BIN
public/icons/transcendence/1/stage-1-hover@2x.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
public/icons/transcendence/1/stage-1-hover@3x.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
public/icons/transcendence/1/stage-1.png
Normal file
|
After Width: | Height: | Size: 765 B |
BIN
public/icons/transcendence/1/stage-1@2x.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
public/icons/transcendence/1/stage-1@3x.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icons/transcendence/2/stage-2-hover.png
Normal file
|
After Width: | Height: | Size: 729 B |
BIN
public/icons/transcendence/2/stage-2-hover@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/icons/transcendence/2/stage-2-hover@3x.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
public/icons/transcendence/2/stage-2.png
Normal file
|
After Width: | Height: | Size: 761 B |
BIN
public/icons/transcendence/2/stage-2@2x.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
public/icons/transcendence/2/stage-2@3x.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icons/transcendence/3/stage-3-hover.png
Normal file
|
After Width: | Height: | Size: 720 B |
BIN
public/icons/transcendence/3/stage-3-hover@2x.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/icons/transcendence/3/stage-3-hover@3x.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/icons/transcendence/3/stage-3.png
Normal file
|
After Width: | Height: | Size: 768 B |
BIN
public/icons/transcendence/3/stage-3@2x.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
public/icons/transcendence/3/stage-3@3x.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
public/icons/transcendence/4/stage-4-hover.png
Normal file
|
After Width: | Height: | Size: 694 B |
BIN
public/icons/transcendence/4/stage-4-hover@2x.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
public/icons/transcendence/4/stage-4-hover@3x.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
public/icons/transcendence/4/stage-4.png
Normal file
|
After Width: | Height: | Size: 770 B |
BIN
public/icons/transcendence/4/stage-4@2x.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
public/icons/transcendence/4/stage-4@3x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icons/transcendence/5/stage-5-hover.png
Normal file
|
After Width: | Height: | Size: 668 B |
BIN
public/icons/transcendence/5/stage-5-hover@2x.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/transcendence/5/stage-5-hover@3x.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/icons/transcendence/5/stage-5.png
Normal file
|
After Width: | Height: | Size: 766 B |
BIN
public/icons/transcendence/5/stage-5@2x.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
BIN
public/icons/transcendence/5/stage-5@3x.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/icons/transcendence/interactive/interactive-base.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/icons/transcendence/interactive/interactive-base@2x.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
public/icons/transcendence/interactive/interactive-base@3x.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
public/icons/transcendence/interactive/interactive-piece.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
public/icons/transcendence/interactive/interactive-piece@2x.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/icons/transcendence/interactive/interactive-piece@3x.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
|
|
@ -370,5 +370,6 @@
|
|||
"no_raid": "No raid",
|
||||
"no_user": "Anonymous",
|
||||
"no_job": "No class",
|
||||
"no_value": "No value"
|
||||
"no_value": "No value",
|
||||
"level": "Level"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -371,5 +371,6 @@
|
|||
"no_raid": "マルチなし",
|
||||
"no_user": "無名",
|
||||
"no_job": "ジョブなし",
|
||||
"no_value": "値なし"
|
||||
"no_value": "値なし",
|
||||
"level": "レベル"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -321,3 +321,38 @@ i.tag {
|
|||
backdrop-filter: blur(5px) saturate(100%) brightness(80%) opacity(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
20% {
|
||||
opacity: 0.2;
|
||||
transform: scale(0.4);
|
||||
}
|
||||
40% {
|
||||
opacity: 0.4;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
60% {
|
||||
opacity: 0.6;
|
||||
transform: scale(1);
|
||||
}
|
||||
70% {
|
||||
opacity: 0.8;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
80% {
|
||||
opacity: 0.8;
|
||||
transform: scale(1);
|
||||
}
|
||||
90% {
|
||||
opacity: 0.8;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,3 +26,36 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin hidpiImage(
|
||||
$image,
|
||||
$extension,
|
||||
$width,
|
||||
$height,
|
||||
$position: center,
|
||||
$repeat: no-repeat
|
||||
) {
|
||||
background: url($image + '.' + $extension) $repeat $position;
|
||||
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 2),
|
||||
screen and (min--moz-device-pixel-ratio: 2),
|
||||
screen and (-moz-min-device-pixel-ratio: 2),
|
||||
screen and (-o-min-device-pixel-ratio: 2/1),
|
||||
screen and (min-device-pixel-ratio: 2),
|
||||
screen and (min-resolution: 192dpi),
|
||||
screen and (min-resolution: 2dppx) {
|
||||
background: url($image + '@2x' + '.' + $extension) $repeat $position;
|
||||
background-size: $width $height;
|
||||
}
|
||||
|
||||
@media screen and (-webkit-min-device-pixel-ratio: 3),
|
||||
screen and (min--moz-device-pixel-ratio: 3),
|
||||
screen and (-moz-min-device-pixel-ratio: 3),
|
||||
screen and (-o-min-device-pixel-ratio: 3/1),
|
||||
screen and (min-device-pixel-ratio: 3),
|
||||
screen and (min-resolution: 216dpi),
|
||||
screen and (min-resolution: 3dppx) {
|
||||
background: url($image + '@3x' + '.' + $extension) $repeat $position;
|
||||
background-size: $width $height;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ $unit-half: calc($unit / 2);
|
|||
$unit-2x: $unit * 2;
|
||||
$unit-3x: $unit * 3;
|
||||
$unit-4x: $unit * 4;
|
||||
$unit-5x: $unit * 5;
|
||||
$unit-6x: $unit * 6;
|
||||
$unit-8x: $unit * 8;
|
||||
$unit-10x: $unit * 10;
|
||||
|
|
|
|||
1
types/GridCharacter.d.ts
vendored
|
|
@ -3,6 +3,7 @@ interface GridCharacter {
|
|||
position: number
|
||||
object: Character
|
||||
uncap_level: number
|
||||
transcendence_step: number
|
||||
over_mastery: CharacterOverMastery
|
||||
aetherial_mastery: ExtendedMastery
|
||||
awakening: {
|
||||
|
|
|
|||
1
types/GridSummon.d.ts
vendored
|
|
@ -5,4 +5,5 @@ interface GridSummon {
|
|||
position: number
|
||||
object: Summon
|
||||
uncap_level: number
|
||||
transcendence_step: number
|
||||
}
|
||||
|
|
|
|||
3
types/Summon.d.ts
vendored
|
|
@ -15,16 +15,19 @@ interface Summon {
|
|||
max_hp: number
|
||||
max_hp_flb: number
|
||||
max_hp_ulb: number
|
||||
max_hp_xlb: number
|
||||
}
|
||||
atk: {
|
||||
min_atk: number
|
||||
max_atk: number
|
||||
max_atk_flb: number
|
||||
max_atk_ulb: number
|
||||
max_atk_xlb: number
|
||||
}
|
||||
uncap: {
|
||||
flb: boolean
|
||||
ulb: boolean
|
||||
xlb: boolean
|
||||
}
|
||||
position?: number
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,8 @@ class Api {
|
|||
return axios.post(resourceUrl, {
|
||||
[resource]: {
|
||||
id: id,
|
||||
uncap_level: value
|
||||
uncap_level: value,
|
||||
transcendence_step: 0
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||