(Hotfix) Popover and hovercard fixes (#379)
* Fix job accessory popover, so shields and manatura can be selected again * Don't show AX skill section in weapon hovercard if no AX skill is set * Center uncap indicator under item image and fix hovercard header layout * Fix a bug that prevented all ring bonuses from displaying in hovercard * Fix transcendence_step being set to 0 when updating a character's masteries * Fix weapon modal so you can set AX skills on weapons with rupee or exp gain * Ensure job accessory and transcendence popovers open/close properly
This commit is contained in:
parent
14ad468737
commit
b50ea1fa31
28 changed files with 430 additions and 252 deletions
|
|
@ -41,17 +41,19 @@
|
|||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit * 2;
|
||||
justify-content: space-between;
|
||||
gap: $unit-2x;
|
||||
|
||||
.icons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
gap: $unit;
|
||||
}
|
||||
flex-grow: 0;
|
||||
gap: $unit-half;
|
||||
|
||||
.UncapIndicator {
|
||||
min-width: 100px;
|
||||
.proficiencies {
|
||||
display: flex;
|
||||
gap: $unit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import UncapIndicator from '~components/uncap/UncapIndicator'
|
|||
import WeaponLabelIcon from '~components/weapon/WeaponLabelIcon'
|
||||
|
||||
import styles from './index.module.scss'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
gridObject: GridCharacter | GridSummon | GridWeapon
|
||||
|
|
@ -107,6 +108,61 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
const summonProficiency = (
|
||||
<div className={styles.icons}>
|
||||
<WeaponLabelIcon labelType={Element[object.element]} size="small" />
|
||||
</div>
|
||||
)
|
||||
|
||||
const weaponProficiency = (
|
||||
<div className={styles.icons}>
|
||||
<WeaponLabelIcon labelType={Element[object.element]} size="small" />
|
||||
{'proficiency' in object && !Array.isArray(object.proficiency) && (
|
||||
<WeaponLabelIcon
|
||||
labelType={Proficiency[object.proficiency]}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
const characterProficiency = (
|
||||
<div
|
||||
className={classNames({
|
||||
[styles.icons]: true,
|
||||
})}
|
||||
>
|
||||
<WeaponLabelIcon labelType={Element[object.element]} size="small" />
|
||||
|
||||
{'proficiency' in object && Array.isArray(object.proficiency) && (
|
||||
<WeaponLabelIcon
|
||||
labelType={Proficiency[object.proficiency[0]]}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
|
||||
{'proficiency' in object &&
|
||||
Array.isArray(object.proficiency) &&
|
||||
object.proficiency.length > 1 && (
|
||||
<WeaponLabelIcon
|
||||
labelType={Proficiency[object.proficiency[1]]}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
function proficiency() {
|
||||
switch (type) {
|
||||
case 'character':
|
||||
return characterProficiency
|
||||
case 'summon':
|
||||
return summonProficiency
|
||||
case 'weapon':
|
||||
return weaponProficiency
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<header className={styles.root}>
|
||||
<div className={styles.title}>
|
||||
|
|
@ -117,21 +173,9 @@ const HovercardHeader = ({ gridObject, object, type, ...props }: Props) => {
|
|||
</div>
|
||||
</div>
|
||||
<div className={styles.subInfo}>
|
||||
<div className={styles.icons}>
|
||||
<WeaponLabelIcon labelType={Element[object.element]} />
|
||||
{'proficiency' in object && Array.isArray(object.proficiency) && (
|
||||
<WeaponLabelIcon labelType={Proficiency[object.proficiency[0]]} />
|
||||
)}
|
||||
{'proficiency' in object && !Array.isArray(object.proficiency) && (
|
||||
<WeaponLabelIcon labelType={Proficiency[object.proficiency]} />
|
||||
)}
|
||||
{'proficiency' in object &&
|
||||
Array.isArray(object.proficiency) &&
|
||||
object.proficiency.length > 1 && (
|
||||
<WeaponLabelIcon labelType={Proficiency[object.proficiency[1]]} />
|
||||
)}
|
||||
</div>
|
||||
{proficiency()}
|
||||
<UncapIndicator
|
||||
className="hovercard"
|
||||
type={type}
|
||||
ulb={object.uncap.ulb || false}
|
||||
flb={object.uncap.flb || false}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
.version {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
|
||||
&.content {
|
||||
.header h3 {
|
||||
|
|
@ -12,6 +11,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.bugs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style-type: disc;
|
||||
gap: $unit-half;
|
||||
padding-left: $unit-2x;
|
||||
}
|
||||
|
||||
.contents {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -121,14 +128,6 @@
|
|||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Bugs {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
list-style-type: disc;
|
||||
gap: $unit-half;
|
||||
padding-left: $unit-2x;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,9 @@ const UpdatesPage = () => {
|
|||
'202302U2': {
|
||||
updates: 1,
|
||||
},
|
||||
'1.2.1': {
|
||||
bugs: 5,
|
||||
},
|
||||
}
|
||||
|
||||
function image(
|
||||
|
|
@ -75,6 +78,20 @@ const UpdatesPage = () => {
|
|||
return (
|
||||
<div className={classes}>
|
||||
<h1>{common('about.segmented_control.updates')}</h1>
|
||||
<section className={styles.version} data-version="1.2.1">
|
||||
<div className={styles.header}>
|
||||
<h3>1.2.1</h3>
|
||||
<time>2023/09/01</time>
|
||||
</div>
|
||||
<h2>Bug fixes</h2>
|
||||
<ul className={styles.bugs}>
|
||||
{[...Array(versionUpdates['1.2.1'].bugs)].map((e, i) => (
|
||||
<li key={`1.2.1-bugfix-${i}`}>
|
||||
{updates(`versions.1.2.1.bugs.${i}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
<ContentUpdate
|
||||
version="2023-08L"
|
||||
dateString="2023/08/31"
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ const CharacterHovercard = (props: Props) => {
|
|||
{[...Array(4)].map((e, i) => {
|
||||
const ringIndex = i + 1
|
||||
const ringStat: ExtendedMastery =
|
||||
props.gridCharacter.over_mastery[i]
|
||||
props.gridCharacter.over_mastery[ringIndex]
|
||||
|
||||
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
|
||||
if (ringIndex === 1 || ringIndex === 2) {
|
||||
return masteryElement(overMastery.a, ringStat)
|
||||
|
|
|
|||
|
|
@ -73,7 +73,9 @@ const CharacterModal = ({
|
|||
const [earring, setEarring] = useState<ExtendedMastery>(emptyExtendedMastery)
|
||||
const [awakening, setAwakening] = useState<Awakening>()
|
||||
const [awakeningLevel, setAwakeningLevel] = useState(1)
|
||||
const [transcendenceStep, setTranscendenceStep] = useState(0)
|
||||
const [transcendenceStep, setTranscendenceStep] = useState(
|
||||
gridCharacter.transcendence_step
|
||||
)
|
||||
|
||||
// Refs
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
min-width: 20vw;
|
||||
max-width: 32vw;
|
||||
max-width: 40vw;
|
||||
padding: $unit * 4;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,93 @@
|
|||
.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);
|
||||
outline: none;
|
||||
padding: $unit;
|
||||
transform-origin: var(--radix-popover-content-transform-origin);
|
||||
width: var(--radix-popover-trigger-width);
|
||||
z-index: 5;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
&.raid {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&.flush {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.jobAccessory {
|
||||
padding: $unit-2x;
|
||||
min-width: 40vw;
|
||||
max-width: 40vw;
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: none;
|
||||
margin-left: $unit-2x;
|
||||
|
||||
h3 {
|
||||
font-size: $font-regular;
|
||||
font-weight: $medium;
|
||||
margin: 0 0 $unit $unit;
|
||||
}
|
||||
|
||||
&.readOnly {
|
||||
min-width: inherit;
|
||||
max-width: inherit;
|
||||
}
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
width: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
@include breakpoint(phone) {
|
||||
width: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.transcendence {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-half;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $unit-10x;
|
||||
height: $unit-10x;
|
||||
|
||||
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);
|
||||
outline: none;
|
||||
padding: $unit;
|
||||
transform-origin: var(--radix-popover-content-transform-origin);
|
||||
z-index: 32;
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-small;
|
||||
font-weight: $medium;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: $yellow;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
fill: var(--dialog-bg);
|
||||
filter: drop-shadow(0px 1px 1px rgb(0 0 0 / 0.18));
|
||||
|
|
|
|||
|
|
@ -20,9 +20,13 @@ export const PopoverContent = React.forwardRef<HTMLDivElement, Props>(
|
|||
{ children, ...props }: PropsWithChildren<Props>,
|
||||
forwardedRef
|
||||
) {
|
||||
const classes = classnames(props.className, {
|
||||
Popover: true,
|
||||
})
|
||||
const classes = classnames(
|
||||
{
|
||||
[styles.popover]: true,
|
||||
},
|
||||
props.className?.split(' ').map((a) => styles[a])
|
||||
)
|
||||
|
||||
|
||||
return (
|
||||
<PopoverPrimitive.Portal>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
.JobAccessoryItem {
|
||||
.item {
|
||||
background: none;
|
||||
border-radius: $input-corner;
|
||||
border: none;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ const JobAccessoryItem = ({ accessory, selected }: Props) => {
|
|||
|
||||
return (
|
||||
<RadioGroup.Item
|
||||
className="JobAccessoryItem"
|
||||
className={styles.item}
|
||||
data-state={selected ? 'checked' : 'unchecked'}
|
||||
value={accessory.id}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,67 +1,37 @@
|
|||
.JobAccessory.Popover {
|
||||
padding: $unit-2x;
|
||||
min-width: 40vw;
|
||||
max-width: 40vw;
|
||||
max-height: 40vh;
|
||||
overflow-y: scroll;
|
||||
margin-left: $unit-2x;
|
||||
|
||||
h3 {
|
||||
font-size: $font-regular;
|
||||
font-weight: $medium;
|
||||
margin: 0 0 $unit $unit;
|
||||
}
|
||||
|
||||
&.ReadOnly {
|
||||
min-width: inherit;
|
||||
max-width: inherit;
|
||||
}
|
||||
.accessories {
|
||||
display: grid;
|
||||
gap: $unit;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
width: initial;
|
||||
max-width: initial;
|
||||
grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.equippedAccessory {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@include breakpoint(phone) {
|
||||
width: initial;
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
.Accessories {
|
||||
display: grid;
|
||||
gap: $unit;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
grid-template-columns: repeat(auto-fit, minmax(90px, 1fr));
|
||||
gap: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.EquippedAccessory {
|
||||
.accessory {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
gap: $unit;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
h4 {
|
||||
font-size: $font-small;
|
||||
font-weight: $medium;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.Accessory {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
|
||||
h4 {
|
||||
font-size: $font-small;
|
||||
font-weight: $medium;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: $item-corner;
|
||||
width: 150px;
|
||||
}
|
||||
img {
|
||||
border-radius: $item-corner;
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,8 +49,8 @@ const JobAccessoryPopover = ({
|
|||
const [open, setOpen] = useState(false)
|
||||
|
||||
const classes = classNames({
|
||||
JobAccessory: true,
|
||||
ReadOnly: !editable,
|
||||
jobAccessory: true,
|
||||
readOnly: !editable,
|
||||
})
|
||||
|
||||
// Hooks
|
||||
|
|
@ -91,7 +91,7 @@ const JobAccessoryPopover = ({
|
|||
)}
|
||||
</h3>
|
||||
<RadioGroup.Root
|
||||
className="Accessories"
|
||||
className={styles.accessories}
|
||||
onValueChange={handleAccessorySelected}
|
||||
>
|
||||
{accessories.map((accessory) => (
|
||||
|
|
@ -110,14 +110,14 @@ const JobAccessoryPopover = ({
|
|||
)
|
||||
|
||||
const readOnly = currentAccessory ? (
|
||||
<div className="EquippedAccessory">
|
||||
<div className={styles.equippedAccessory}>
|
||||
<h3>
|
||||
{t('equipped')}{' '}
|
||||
{job.accessory_type === 1
|
||||
? `${t('accessories.paladin')}s`
|
||||
: t('accessories.manadiver')}
|
||||
</h3>
|
||||
<div className="Accessory">
|
||||
<div className={styles.accessory}>
|
||||
<img
|
||||
alt={currentAccessory.name[locale]}
|
||||
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/accessory-grid/${currentAccessory.granblue_id}.jpg`}
|
||||
|
|
|
|||
|
|
@ -93,6 +93,7 @@ const JobImage = ({
|
|||
editable={editable}
|
||||
open={open}
|
||||
job={job}
|
||||
key={`accessory-${open}`}
|
||||
onAccessorySelected={onAccessorySelected}
|
||||
onOpenChange={handlePopoverOpenChanged}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -70,8 +70,12 @@ const AXSelect = (props: Props) => {
|
|||
// States
|
||||
const [primaryAxModifier, setPrimaryAxModifier] = useState(-1)
|
||||
const [secondaryAxModifier, setSecondaryAxModifier] = useState(-1)
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(0.0)
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(0.0)
|
||||
const [primaryAxValue, setPrimaryAxValue] = useState(
|
||||
props.currentSkills ? props.currentSkills[0].strength : 0.0
|
||||
)
|
||||
const [secondaryAxValue, setSecondaryAxValue] = useState(
|
||||
props.currentSkills ? props.currentSkills[1].strength : 0.0
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setupAx1()
|
||||
|
|
@ -146,7 +150,10 @@ const AXSelect = (props: Props) => {
|
|||
// Classes
|
||||
const secondarySetClasses = classNames({
|
||||
[styles.set]: true,
|
||||
[styles.hidden]: primaryAxModifier < 0,
|
||||
[styles.hidden]:
|
||||
primaryAxModifier < 0 ||
|
||||
primaryAxModifier === 18 ||
|
||||
primaryAxModifier === 19,
|
||||
})
|
||||
|
||||
function setupAx1() {
|
||||
|
|
@ -270,9 +277,12 @@ const AXSelect = (props: Props) => {
|
|||
secondaryAxModifierSelect.current &&
|
||||
secondaryAxValueInput.current
|
||||
) {
|
||||
setupInput(ax[props.axType - 1][value], primaryAxValueInput.current)
|
||||
setupInput(
|
||||
ax[props.axType - 1].find((ax) => ax.id === value),
|
||||
primaryAxValueInput.current
|
||||
)
|
||||
|
||||
setPrimaryAxValue(0)
|
||||
primaryAxValueInput.current.value = ''
|
||||
|
||||
// Reset the secondary AX modifier, reset the AX value and hide the input
|
||||
setSecondaryAxModifier(-1)
|
||||
|
|
@ -302,7 +312,7 @@ const AXSelect = (props: Props) => {
|
|||
const value = parseFloat(event.target.value)
|
||||
let newErrors = { ...errors }
|
||||
|
||||
if (primaryAxValueInput.current == event.target) {
|
||||
if (primaryAxValueInput.current === event.target) {
|
||||
if (handlePrimaryErrors(value)) setPrimaryAxValue(value)
|
||||
} else {
|
||||
if (handleSecondaryErrors(value)) setSecondaryAxValue(value)
|
||||
|
|
@ -310,16 +320,18 @@ const AXSelect = (props: Props) => {
|
|||
}
|
||||
|
||||
function handlePrimaryErrors(value: number) {
|
||||
const primaryAxSkill = ax[props.axType - 1][primaryAxModifier]
|
||||
const primaryAxSkill = ax[props.axType - 1].find(
|
||||
(ax) => ax.id === primaryAxModifier
|
||||
)
|
||||
let newErrors = { ...errors }
|
||||
|
||||
if (value < primaryAxSkill.minValue) {
|
||||
if (primaryAxSkill && value < primaryAxSkill.minValue) {
|
||||
newErrors.axValue1 = t('ax.errors.value_too_low', {
|
||||
name: primaryAxSkill.name[locale],
|
||||
minValue: primaryAxSkill.minValue,
|
||||
suffix: primaryAxSkill.suffix ? primaryAxSkill.suffix : '',
|
||||
})
|
||||
} else if (value > primaryAxSkill.maxValue) {
|
||||
} else if (primaryAxSkill && value > primaryAxSkill.maxValue) {
|
||||
newErrors.axValue1 = t('ax.errors.value_too_high', {
|
||||
name: primaryAxSkill.name[locale],
|
||||
maxValue: primaryAxSkill.maxValue,
|
||||
|
|
@ -327,7 +339,7 @@ const AXSelect = (props: Props) => {
|
|||
})
|
||||
} else if (!value || value <= 0) {
|
||||
newErrors.axValue1 = t('ax.errors.value_empty', {
|
||||
name: primaryAxSkill.name[locale],
|
||||
name: primaryAxSkill?.name[locale],
|
||||
})
|
||||
} else {
|
||||
newErrors.axValue1 = ''
|
||||
|
|
@ -380,6 +392,7 @@ const AXSelect = (props: Props) => {
|
|||
}
|
||||
|
||||
function setupInput(ax: ItemSkill | undefined, element: HTMLInputElement) {
|
||||
console.log(ax)
|
||||
if (ax) {
|
||||
const rangeString = `${ax.minValue}~${ax.maxValue}${ax.suffix || ''}`
|
||||
|
||||
|
|
@ -431,11 +444,7 @@ const AXSelect = (props: Props) => {
|
|||
hidden: primaryAxModifier < 0,
|
||||
})}
|
||||
bound={true}
|
||||
value={
|
||||
props.currentSkills && props.currentSkills[0]
|
||||
? props.currentSkills[0].strength
|
||||
: 0
|
||||
}
|
||||
value={primaryAxValue}
|
||||
type="number"
|
||||
onChange={handleInputChange}
|
||||
ref={primaryAxValueInput}
|
||||
|
|
@ -469,11 +478,7 @@ const AXSelect = (props: Props) => {
|
|||
hidden: secondaryAxModifier < 0,
|
||||
})}
|
||||
bound={true}
|
||||
value={
|
||||
props.currentSkills && props.currentSkills[1]
|
||||
? props.currentSkills[1].strength
|
||||
: 0
|
||||
}
|
||||
value={secondaryAxValue}
|
||||
type="number"
|
||||
onChange={handleInputChange}
|
||||
ref={secondaryAxValueInput}
|
||||
|
|
|
|||
|
|
@ -1,37 +1,3 @@
|
|||
.transcendence {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-half;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $unit-10x;
|
||||
height: $unit-10x;
|
||||
|
||||
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);
|
||||
outline: none;
|
||||
padding: $unit;
|
||||
transform-origin: var(--radix-popover-content-transform-origin);
|
||||
z-index: 32;
|
||||
|
||||
&.open {
|
||||
opacity: 1;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: $font-small;
|
||||
font-weight: $medium;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: $yellow;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes scaleIn {
|
||||
0% {
|
||||
opacity: 0;
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ interface Props
|
|||
HTMLDivElement
|
||||
> {
|
||||
type: 'character' | 'summon'
|
||||
starRef: React.RefObject<HTMLDivElement>
|
||||
open: boolean
|
||||
stage: number
|
||||
onOpenChange?: (open: boolean) => void
|
||||
|
|
@ -24,8 +25,9 @@ interface Props
|
|||
}
|
||||
|
||||
const TranscendencePopover = ({
|
||||
children,
|
||||
open: popoverOpen,
|
||||
starRef,
|
||||
children,
|
||||
type,
|
||||
stage,
|
||||
tabIndex,
|
||||
|
|
@ -45,8 +47,8 @@ const TranscendencePopover = ({
|
|||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (open) popoverRef.current?.focus()
|
||||
}, [])
|
||||
setOpen(popoverOpen)
|
||||
}, [popoverOpen])
|
||||
|
||||
useEffect(() => {
|
||||
if (stage) setCurrentStage(stage)
|
||||
|
|
@ -57,10 +59,6 @@ const TranscendencePopover = ({
|
|||
else if (type === 'summon') setBaseLevel(200)
|
||||
}, [type])
|
||||
|
||||
useEffect(() => {
|
||||
setOpen(popoverOpen)
|
||||
}, [popoverOpen])
|
||||
|
||||
function handleFragmentClicked(newStage: number) {
|
||||
setCurrentStage(newStage)
|
||||
if (sendValue) sendValue(newStage)
|
||||
|
|
@ -70,13 +68,33 @@ const TranscendencePopover = ({
|
|||
setCurrentStage(newStage)
|
||||
}
|
||||
|
||||
function closePopover() {
|
||||
setOpen(false)
|
||||
if (onOpenChange) onOpenChange(false)
|
||||
}
|
||||
|
||||
function handlePointerDownOutside(
|
||||
event: CustomEvent<{ originalEvent: PointerEvent }>
|
||||
) {
|
||||
const target = event.detail.originalEvent.target as Element
|
||||
if (
|
||||
target &&
|
||||
starRef.current &&
|
||||
target.closest('.TranscendenceStar') !== starRef.current
|
||||
) {
|
||||
closePopover()
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={onOpenChange}>
|
||||
<Popover open={open}>
|
||||
<PopoverAnchor>{children}</PopoverAnchor>
|
||||
<PopoverContent
|
||||
className={styles.transcendence}
|
||||
className="transcendence"
|
||||
ref={popoverRef}
|
||||
tabIndex={tabIndex}
|
||||
onEscapeKeyDown={closePopover}
|
||||
onPointerDownOutside={handlePointerDownOutside}
|
||||
>
|
||||
<TranscendenceStar
|
||||
className="interactive base"
|
||||
|
|
|
|||
|
|
@ -20,97 +20,102 @@ interface Props
|
|||
|
||||
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({
|
||||
[styles.star]: true,
|
||||
[styles.immutable]: immutable,
|
||||
[styles.empty]: stage === 0,
|
||||
[styles.stage1]: stage === 1,
|
||||
[styles.stage2]: stage === 2,
|
||||
[styles.stage3]: stage === 3,
|
||||
[styles.stage4]: stage === 4,
|
||||
[styles.stage5]: stage === 5,
|
||||
})
|
||||
|
||||
const baseImageClasses = classnames(
|
||||
const TranscendenceStar = React.forwardRef<HTMLDivElement, Props>(
|
||||
function TranscendenceStar(
|
||||
{
|
||||
[styles.figure]: true,
|
||||
},
|
||||
className?.split(' ').map((c) => styles[c])
|
||||
)
|
||||
className,
|
||||
interactive,
|
||||
stage,
|
||||
editable,
|
||||
tabIndex,
|
||||
onStarClick,
|
||||
onFragmentClick,
|
||||
onFragmentHover,
|
||||
}: Props,
|
||||
forwardedRef
|
||||
) {
|
||||
const [visibleStage, setVisibleStage] = useState(0)
|
||||
const [currentStage, setCurrentStage] = useState(0)
|
||||
const [immutable, setImmutable] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleStage(stage)
|
||||
setCurrentStage(stage)
|
||||
}, [stage])
|
||||
// Classes
|
||||
const starClasses = classnames({
|
||||
TranscendenceStar: true,
|
||||
[styles.star]: true,
|
||||
[styles.immutable]: immutable,
|
||||
[styles.empty]: stage === 0,
|
||||
[styles.stage1]: stage === 1,
|
||||
[styles.stage2]: stage === 2,
|
||||
[styles.stage3]: stage === 3,
|
||||
[styles.stage4]: stage === 4,
|
||||
[styles.stage5]: stage === 5,
|
||||
})
|
||||
|
||||
function handleClick() {
|
||||
if (onStarClick) {
|
||||
onStarClick()
|
||||
const baseImageClasses = classnames(
|
||||
{
|
||||
[styles.figure]: true,
|
||||
},
|
||||
className?.split(' ').map((c) => styles[c])
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setVisibleStage(stage)
|
||||
setCurrentStage(stage)
|
||||
}, [stage])
|
||||
|
||||
function handleClick() {
|
||||
if (onStarClick) onStarClick()
|
||||
}
|
||||
}
|
||||
|
||||
function handleFragmentClick(index: number) {
|
||||
let newStage = index
|
||||
if (index === currentStage) newStage = 0
|
||||
function handleFragmentClick(index: number) {
|
||||
let newStage = index
|
||||
if (index === currentStage) newStage = 0
|
||||
|
||||
setVisibleStage(newStage)
|
||||
setCurrentStage(newStage)
|
||||
if (onFragmentClick) onFragmentClick(newStage)
|
||||
}
|
||||
setVisibleStage(newStage)
|
||||
setCurrentStage(newStage)
|
||||
if (onFragmentClick) onFragmentClick(newStage)
|
||||
}
|
||||
|
||||
function handleFragmentHover(index: number) {
|
||||
setVisibleStage(index)
|
||||
if (onFragmentHover) onFragmentHover(index)
|
||||
}
|
||||
function handleFragmentHover(index: number) {
|
||||
setVisibleStage(index)
|
||||
if (onFragmentHover) onFragmentHover(index)
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
setVisibleStage(currentStage)
|
||||
if (onFragmentHover) onFragmentHover(currentStage)
|
||||
}
|
||||
function handleMouseLeave() {
|
||||
setVisibleStage(currentStage)
|
||||
if (onFragmentHover) onFragmentHover(currentStage)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={starClasses}
|
||||
onClick={editable ? handleClick : () => {}}
|
||||
onMouseLeave={interactive ? handleMouseLeave : () => {}}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<div className={styles.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}
|
||||
/>
|
||||
) : (
|
||||
''
|
||||
)
|
||||
})}
|
||||
return (
|
||||
<div
|
||||
className={starClasses}
|
||||
onClick={editable ? handleClick : () => {}}
|
||||
onMouseLeave={interactive ? handleMouseLeave : () => {}}
|
||||
ref={forwardedRef}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<div className={styles.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>
|
||||
<i className={baseImageClasses} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
TranscendenceStar.defaultProps = {
|
||||
stage: 0,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
.wrapper {
|
||||
display: flex;
|
||||
|
||||
&.hovercard {
|
||||
min-width: 100px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
|||
|
|
@ -4,8 +4,9 @@ import TranscendencePopover from '~components/uncap/TranscendencePopover'
|
|||
import TranscendenceStar from '~components/uncap/TranscendenceStar'
|
||||
|
||||
import styles from './index.module.scss'
|
||||
import classNames from 'classnames'
|
||||
|
||||
interface Props {
|
||||
interface Props extends React.ComponentProps<'div'> {
|
||||
type: 'character' | 'weapon' | 'summon'
|
||||
rarity?: number
|
||||
uncapLevel?: number
|
||||
|
|
@ -25,6 +26,15 @@ const UncapIndicator = (props: Props) => {
|
|||
|
||||
const [popoverOpen, setPopoverOpen] = useState(false)
|
||||
|
||||
const transcendenceStarRef = React.createRef<HTMLDivElement>()
|
||||
|
||||
const classes = classNames(
|
||||
{
|
||||
[styles.wrapper]: true,
|
||||
},
|
||||
props.className?.split(' ').map((className) => styles[className])
|
||||
)
|
||||
|
||||
function setNumStars() {
|
||||
let numStars
|
||||
|
||||
|
|
@ -74,7 +84,11 @@ const UncapIndicator = (props: Props) => {
|
|||
|
||||
function sendTranscendenceStage(stage: number) {
|
||||
if (props.updateTranscendence) props.updateTranscendence(stage)
|
||||
togglePopover(false)
|
||||
setPopoverOpen(false)
|
||||
}
|
||||
|
||||
function handleStarClicked() {
|
||||
if (props.editable) togglePopover(!popoverOpen)
|
||||
}
|
||||
|
||||
const transcendence = (i: number) => {
|
||||
|
|
@ -82,25 +96,27 @@ const UncapIndicator = (props: Props) => {
|
|||
return props.type === 'character' || props.type === 'summon' ? (
|
||||
<TranscendencePopover
|
||||
open={popoverOpen}
|
||||
stage={props.transcendenceStage ? props.transcendenceStage : 0}
|
||||
stage={props.transcendenceStage || 0}
|
||||
type={props.type}
|
||||
onOpenChange={togglePopover}
|
||||
sendValue={sendTranscendenceStage}
|
||||
key={`star_${i}`}
|
||||
key={`popover_${i}_${popoverOpen}`}
|
||||
starRef={transcendenceStarRef}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
<TranscendenceStar
|
||||
key={`star_${i}`}
|
||||
stage={props.transcendenceStage}
|
||||
stage={props.transcendenceStage || 0}
|
||||
editable={props.editable}
|
||||
interactive={false}
|
||||
onStarClick={() => togglePopover(true)}
|
||||
ref={transcendenceStarRef}
|
||||
onStarClick={handleStarClicked}
|
||||
/>
|
||||
</TranscendencePopover>
|
||||
) : (
|
||||
<TranscendenceStar
|
||||
key={`star_${i}`}
|
||||
stage={props.transcendenceStage}
|
||||
stage={props.transcendenceStage || 0}
|
||||
editable={props.editable}
|
||||
interactive={false}
|
||||
tabIndex={tabIndex}
|
||||
|
|
@ -150,7 +166,7 @@ const UncapIndicator = (props: Props) => {
|
|||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={classes}>
|
||||
<ul className={styles.indicator}>
|
||||
{Array.from(Array(numStars)).map((x, i) => {
|
||||
if (props.type === 'character' && i > 4) {
|
||||
|
|
|
|||
|
|
@ -229,8 +229,8 @@ const WeaponHovercard = (props: Props) => {
|
|||
/>
|
||||
{props.gridWeapon.object.ax &&
|
||||
props.gridWeapon.ax &&
|
||||
props.gridWeapon.ax[0].modifier !== undefined &&
|
||||
props.gridWeapon.ax[0].strength !== undefined &&
|
||||
props.gridWeapon.ax[0].modifier !== null &&
|
||||
props.gridWeapon.ax[0].strength !== null &&
|
||||
axSection}
|
||||
{props.gridWeapon.awakening && awakeningSection}
|
||||
{props.gridWeapon.weapon_keys &&
|
||||
|
|
|
|||
|
|
@ -4,6 +4,12 @@
|
|||
height: 25px;
|
||||
width: 60px;
|
||||
|
||||
&.small {
|
||||
background-size: 50px 20px;
|
||||
height: 20px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
/* Elements */
|
||||
|
||||
&.fire.en {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import styles from './index.module.scss'
|
|||
|
||||
interface Props {
|
||||
labelType: string
|
||||
size: 'small' | 'normal'
|
||||
}
|
||||
|
||||
const WeaponLabelIcon = (props: Props) => {
|
||||
|
|
@ -13,6 +14,7 @@ const WeaponLabelIcon = (props: Props) => {
|
|||
|
||||
const classes = classNames({
|
||||
[styles.icon]: true,
|
||||
[styles.small]: props.size === 'small',
|
||||
[styles[props.labelType]]: true,
|
||||
[styles.en]: router.locale === 'en',
|
||||
[styles.ja]: router.locale === 'ja',
|
||||
|
|
@ -21,4 +23,8 @@ const WeaponLabelIcon = (props: Props) => {
|
|||
return <i className={classes} />
|
||||
}
|
||||
|
||||
WeaponLabelIcon.defaultProps = {
|
||||
size: 'normal',
|
||||
}
|
||||
|
||||
export default WeaponLabelIcon
|
||||
|
|
|
|||
|
|
@ -448,7 +448,8 @@ const WeaponUnit = ({
|
|||
gridWeapon.ax &&
|
||||
gridWeapon.ax.length > 0
|
||||
) {
|
||||
for (let i = 0; i < gridWeapon.ax.length; i++) {
|
||||
const numSkills = gridWeapon.ax[1].modifier ? 2 : 1
|
||||
for (let i = 0; i < numSkills; i++) {
|
||||
const image = axImage(i)
|
||||
if (image) images.push(image)
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
|
@ -19,6 +19,16 @@
|
|||
"uncap": "Uncap"
|
||||
},
|
||||
"versions": {
|
||||
"1.2.1": {
|
||||
"bugs": [
|
||||
"Job accessory popover has been fixed, so Paladin shields and Manadiver manatura can be selected again",
|
||||
"The AX skill section no longer shows up in the weapon hovercard if no AX skills are set",
|
||||
"The top of the character hovercard has been slightly refined",
|
||||
"Fixed a bug that prevented all character over mastery (ring) bonuses from being displayed",
|
||||
"Fixed a bug that reset a character's transcendence level if their mastery values are set",
|
||||
"Fixed a bug that prevented setting the value for Rupee Gain or EXP Gain AX skills on weapons"
|
||||
]
|
||||
},
|
||||
"1.2.0": {
|
||||
"notes": "I'm very bad at writing actual release notes, so this is a consolidation of the bigger features released in the last six months. Don't worry: there's some new stuff here too!\nThe next features (in no particular order) will be: a way to define roles and substitutions for characters, a collection tracker and hopefully making progress on guides. That's a lot, but I'll chip away at it bit by bit.\nAs always, if you have any feedback, feel free to reach out in the granblue-tools Discord. Thanks for using granblue.team!",
|
||||
"features": [
|
||||
|
|
|
|||
|
|
@ -19,6 +19,16 @@
|
|||
"uncap": "上限解放"
|
||||
},
|
||||
"versions": {
|
||||
"1.2.1": {
|
||||
"bugs": [
|
||||
"ジョブアクセサリーのメニューが修正され、パラディンの盾やマナダイバーのマナベリーが再び選択できるようになりました",
|
||||
"EXスキルが設定されていない場合、武器ホバーカードにEXスキルの項目が表示されなくなりました",
|
||||
"キャラクターホバーカードのトップが少し洗練されました",
|
||||
"キャラクターホバーカードにリミットボーナスを全部表示しないバグを修正しました",
|
||||
"キャラクターのボーナス値が設定されている場合、キャラクターの超越レベルがリセットされるバグを修正しました",
|
||||
"武器にルピーUPまたはEXP UPのEXスキルの値を設定できないバグを修正しました"
|
||||
]
|
||||
},
|
||||
"1.2.0": {
|
||||
"notes": "I'm very bad at writing actual release notes, so this is a consolidation of the bigger features released in the last six months. Don't worry: there's some new stuff here too!\nThe next features (in no particular order) will be: a way to define roles and substitutions for characters, a collection tracker and hopefully making progress on guides. That's a lot, but I'll chip away at it bit by bit.\nAs always, if you have any feedback, feel free to reach out in the granblue-tools Discord. Thanks for using granblue.team!",
|
||||
"features": [
|
||||
|
|
|
|||
Loading…
Reference in a new issue