Merge pull request #198 from jedmund/hovercard-revamp

Revamp hovercards
This commit is contained in:
Justin Edmund 2023-01-29 22:50:31 -08:00 committed by GitHub
commit 12976eab34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 743 additions and 313 deletions

1
.gitignore vendored
View file

@ -54,6 +54,7 @@ public/images/job*
public/images/awakening*
public/images/ax*
public/images/accessory*
public/images/mastery*
# Typescript v1 declaration files
typings/

View file

@ -0,0 +1,68 @@
.Character.HovercardContent {
.title .Image {
position: relative;
.Perpetuity {
position: absolute;
background-image: url('/icons/perpetuity/filled.svg');
background-size: $unit-3x $unit-3x;
z-index: 20;
top: $unit-half * -1;
right: $unit-3x;
width: $unit-3x;
height: $unit-3x;
}
}
.Mastery {
display: flex;
flex-direction: column;
gap: $unit;
ul {
display: flex;
flex-direction: column;
gap: $unit-half;
.ExtendedMastery {
align-items: center;
display: flex;
gap: $unit-half;
img {
width: $unit-3x;
}
strong {
font-weight: $bold;
}
}
}
}
.Awakening {
display: flex;
flex-direction: column;
gap: $unit;
& > div {
align-items: center;
display: flex;
gap: $unit-half;
img {
width: $unit-3x;
}
strong {
font-weight: $bold;
}
}
}
// .Footer {
// position: sticky;
// bottom: 0;
// left: 0;
// }
}

View file

@ -1,17 +1,31 @@
import React from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import jconv from 'jconv'
import * as HoverCard from '@radix-ui/react-hover-card'
import {
Hovercard,
HovercardContent,
HovercardTrigger,
} from '~components/Hovercard'
import Button from '~components/Button'
import WeaponLabelIcon from '~components/WeaponLabelIcon'
import UncapIndicator from '~components/UncapIndicator'
import {
overMastery,
aetherialMastery,
permanentMastery,
} from '~data/overMastery'
import { characterAwakening } from '~data/awakening'
import { ExtendedMastery } from '~types'
import './index.scss'
interface Props {
gridCharacter: GridCharacter
children: React.ReactNode
onTriggerClick: () => void
}
interface KeyNames {
@ -43,10 +57,19 @@ const CharacterHovercard = (props: Props) => {
]
const tintElement = Element[props.gridCharacter.object.element]
const wikiUrl = `https://gbf.wiki/${props.gridCharacter.object.name.en.replaceAll(
' ',
'_'
)}`
function goTo() {
const urlSafeName = props.gridCharacter.object.name.en.replaceAll(' ', '_')
const url = `https://gbf.wiki/${urlSafeName}`
window.open(url, '_blank')
}
const perpetuity = () => {
if (props.gridCharacter && props.gridCharacter.perpetuity) {
return <i className="Perpetuity" />
}
}
function characterImage() {
let imgSrc = ''
@ -66,59 +89,194 @@ const CharacterHovercard = (props: Props) => {
return imgSrc
}
function masteryElement(dictionary: ItemSkill[], mastery: ExtendedMastery) {
const canonicalMastery = dictionary.find(
(item) => item.id === mastery.modifier
)
if (canonicalMastery) {
return (
<li className="ExtendedMastery" key={canonicalMastery.id}>
<img
alt={canonicalMastery.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/mastery/${canonicalMastery.slug}.png`}
/>
<span>
<strong>{canonicalMastery.name[locale]}</strong>&nbsp;
{`+${mastery.strength}${canonicalMastery.suffix}`}
</span>
</li>
)
}
}
const overMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.over_mastery) {
return (
<section className="Mastery">
<h5 className={tintElement}>
{t('modals.characters.subtitles.ring')}
</h5>
<ul>
{[...Array(4)].map((e, i) => {
const ringIndex = i + 1
const ringStat: ExtendedMastery =
props.gridCharacter.over_mastery[i]
if (ringStat && ringStat.modifier && ringStat.modifier > 0) {
if (ringIndex === 1 || ringIndex === 2) {
return masteryElement(overMastery.a, ringStat)
} else if (ringIndex === 3) {
return masteryElement(overMastery.b, ringStat)
} else {
return masteryElement(overMastery.c, ringStat)
}
}
})}
</ul>
</section>
)
}
}
const aetherialMasterySection = () => {
if (
props.gridCharacter &&
props.gridCharacter.aetherial_mastery &&
props.gridCharacter.aetherial_mastery.modifier > 0
) {
return (
<section className="Mastery">
<h5 className={tintElement}>
{t('modals.characters.subtitles.earring')}
</h5>
<ul>
{masteryElement(
aetherialMastery,
props.gridCharacter.aetherial_mastery
)}
</ul>
</section>
)
}
}
const permanentMasterySection = () => {
if (props.gridCharacter && props.gridCharacter.perpetuity) {
return (
<section className="Mastery">
<h5 className={tintElement}>
{t('modals.characters.subtitles.permanent')}
</h5>
<ul>
{[...Array(4)].map((e, i) => {
return masteryElement(permanentMastery, {
modifier: i + 1,
strength: permanentMastery[i].maxValue,
})
})}
</ul>
</section>
)
}
}
const awakeningSection = () => {
const gridAwakening = props.gridCharacter.awakening
const awakening = characterAwakening.find(
(awakening) => awakening.id === gridAwakening?.type
)
if (gridAwakening && awakening) {
return (
<section className="Awakening">
<h5 className={tintElement}>
{t('modals.characters.subtitles.awakening')}
</h5>
<div>
{gridAwakening.type > 1 ? (
<img
alt={awakening.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/character_${gridAwakening.type}.jpg`}
/>
) : (
''
)}
<span>
<strong>{`${awakening.name[locale]}`}</strong>&nbsp;
{`Lv${gridAwakening.level}`}
</span>
</div>
</section>
)
}
}
const wikiButton = (
<Button
className={tintElement}
text={t('buttons.wiki')}
onClick={goTo}
contained={true}
/>
)
return (
<HoverCard.Root>
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content className="Weapon Hovercard">
<div className="top">
<div className="title">
<h4>{props.gridCharacter.object.name[locale]}</h4>
<Hovercard openDelay={350}>
<HovercardTrigger asChild onClick={props.onTriggerClick}>
{props.children}
</HovercardTrigger>
<HovercardContent className="Character">
<div className="top">
<div className="title">
<h4>{props.gridCharacter.object.name[locale]}</h4>
<div className="Image">
{perpetuity()}
<img
alt={props.gridCharacter.object.name[locale]}
src={characterImage()}
/>
</div>
<div className="subInfo">
<div className="icons">
<WeaponLabelIcon
labelType={Element[props.gridCharacter.object.element]}
/>
</div>
<div className="subInfo">
<div className="icons">
<WeaponLabelIcon
labelType={Element[props.gridCharacter.object.element]}
/>
<WeaponLabelIcon
labelType={
Proficiency[
props.gridCharacter.object.proficiency.proficiency1
]
}
/>
{props.gridCharacter.object.proficiency.proficiency2 ? (
<WeaponLabelIcon
labelType={
Proficiency[
props.gridCharacter.object.proficiency.proficiency1
props.gridCharacter.object.proficiency.proficiency2
]
}
/>
{props.gridCharacter.object.proficiency.proficiency2 ? (
<WeaponLabelIcon
labelType={
Proficiency[
props.gridCharacter.object.proficiency.proficiency2
]
}
/>
) : (
''
)}
</div>
<UncapIndicator
type="character"
ulb={props.gridCharacter.object.uncap.ulb || false}
flb={props.gridCharacter.object.uncap.flb || false}
special={false}
/>
) : (
''
)}
</div>
<UncapIndicator
type="character"
ulb={props.gridCharacter.object.uncap.ulb || false}
flb={props.gridCharacter.object.uncap.flb || false}
transcendenceStage={props.gridCharacter.transcendence_step}
special={props.gridCharacter.object.special}
/>
</div>
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
{t('buttons.wiki')}
</a>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard.Root>
</div>
{wikiButton}
{awakeningSection()}
{overMasterySection()}
{aetherialMasterySection()}
{permanentMasterySection()}
</HovercardContent>
</Hovercard>
)
}

View file

@ -95,7 +95,7 @@ const CharacterUnit = ({
setDetailsModalOpen(true)
}
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
function openSearchModal() {
if (editable) setSearchModalOpen(true)
}
@ -286,33 +286,50 @@ const CharacterUnit = ({
}
}
const image = (
<div
className="CharacterImage"
onClick={openSearchModal}
tabIndex={gridCharacter ? gridCharacter.position * 7 : 0}
>
const image = () => {
let image = (
<img
alt={character?.name[locale]}
className="grid_image"
src={imageUrl}
/>
{editable ? (
<span className="icon">
<PlusIcon />
</span>
) : (
''
)}
</div>
)
)
const content = (
<div
className="CharacterImage"
tabIndex={gridCharacter ? gridCharacter.position * 7 : 0}
onClick={openSearchModal}
>
{image}
{editable ? (
<span className="icon">
<PlusIcon />
</span>
) : (
''
)}
</div>
)
return gridCharacter ? (
<CharacterHovercard
gridCharacter={gridCharacter}
onTriggerClick={openSearchModal}
>
{content}
</CharacterHovercard>
) : (
content
)
}
const unitContent = (
<>
<div className={classes}>
{contextMenu()}
{perpetuity()}
{image}
{image()}
{gridCharacter && character ? (
<UncapIndicator
type="character"
@ -335,13 +352,7 @@ const CharacterUnit = ({
</>
)
const unitContentWithHovercard = (
<CharacterHovercard gridCharacter={gridCharacter!}>
{unitContent}
</CharacterHovercard>
)
return gridCharacter && !editable ? unitContentWithHovercard : unitContent
return unitContent
}
export default CharacterUnit

View file

@ -0,0 +1,91 @@
.HovercardContent {
animation: scaleIn $duration-zoom ease-out;
transform-origin: var(--radix-hover-card-content-transform-origin);
background: var(--dialog-bg);
border-radius: $card-corner;
color: var(--text-primary);
display: flex;
flex-direction: column;
gap: $unit-2x;
max-height: 30vh;
overflow-y: scroll;
padding: $unit-2x;
width: 300px;
.top {
display: flex;
flex-direction: column;
gap: calc($unit / 2);
.title {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit * 2;
h4 {
flex-grow: 1;
font-size: $font-medium;
line-height: 1.2;
min-width: 140px;
}
img {
height: auto;
width: 100px;
}
}
.subInfo {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit * 2;
.icons {
display: flex;
flex-direction: row;
flex-grow: 1;
gap: $unit;
}
}
}
section {
h5 {
font-size: $font-small;
font-weight: $medium;
opacity: 0.7;
&.wind {
color: $wind-bg-20;
}
&.fire {
color: $fire-bg-20;
}
&.water {
color: $water-bg-20;
}
&.earth {
color: $earth-bg-20;
}
&.dark {
color: $dark-bg-10;
}
&.light {
color: $light-bg-20;
}
}
}
a.Button {
display: block;
padding: $unit * 1.5;
text-align: center;
}
}

View file

@ -0,0 +1,31 @@
import React, { PropsWithChildren } from 'react'
import classNames from 'classnames'
import * as HoverCardPrimitive from '@radix-ui/react-hover-card'
import './index.scss'
interface Props extends HoverCardPrimitive.HoverCardContentProps {}
export const Hovercard = HoverCardPrimitive.Root
export const HovercardTrigger = HoverCardPrimitive.Trigger
export const HovercardContent = ({
children,
...props
}: PropsWithChildren<Props>) => {
const classes = classNames(props.className, {
HovercardContent: true,
})
return (
<HoverCardPrimitive.Portal>
<HoverCardPrimitive.Content
{...props}
className={classes}
sideOffset={4}
collisionPadding={{ top: 16, left: 16, right: 16, bottom: 16 }}
>
{children}
</HoverCardPrimitive.Content>
</HoverCardPrimitive.Portal>
)
}

View file

@ -2,8 +2,12 @@ import React from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import * as HoverCard from '@radix-ui/react-hover-card'
import {
Hovercard,
HovercardContent,
HovercardTrigger,
} from '~components/Hovercard'
import Button from '~components/Button'
import WeaponLabelIcon from '~components/WeaponLabelIcon'
import UncapIndicator from '~components/UncapIndicator'
@ -12,6 +16,7 @@ import './index.scss'
interface Props {
gridSummon: GridSummon
children: React.ReactNode
onTriggerClick: () => void
}
const SummonHovercard = (props: Props) => {
@ -23,10 +28,13 @@ const SummonHovercard = (props: Props) => {
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const tintElement = Element[props.gridSummon.object.element]
const wikiUrl = `https://gbf.wiki/${props.gridSummon.object.name.en.replaceAll(
' ',
'_'
)}`
function goTo() {
const urlSafeName = props.gridSummon.object.name.en.replaceAll(' ', '_')
const url = `https://gbf.wiki/${urlSafeName}`
window.open(url, '_blank')
}
function summonImage() {
let imgSrc = ''
@ -59,40 +67,46 @@ const SummonHovercard = (props: Props) => {
return imgSrc
}
const wikiButton = (
<Button
className={tintElement}
text={t('buttons.wiki')}
onClick={goTo}
contained={true}
/>
)
return (
<HoverCard.Root>
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content className="Weapon Hovercard">
<div className="top">
<div className="title">
<h4>{props.gridSummon.object.name[locale]}</h4>
<img
alt={props.gridSummon.object.name[locale]}
src={summonImage()}
/>
</div>
<div className="subInfo">
<div className="icons">
<WeaponLabelIcon
labelType={Element[props.gridSummon.object.element]}
/>
</div>
<UncapIndicator
type="summon"
ulb={props.gridSummon.object.uncap.ulb || false}
flb={props.gridSummon.object.uncap.flb || false}
special={false}
/>
</div>
<Hovercard openDelay={350}>
<HovercardTrigger asChild onClick={props.onTriggerClick}>
{props.children}
</HovercardTrigger>
<HovercardContent className="Summon">
<div className="top">
<div className="title">
<h4>{props.gridSummon.object.name[locale]}</h4>
<img
alt={props.gridSummon.object.name[locale]}
src={summonImage()}
/>
</div>
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
{t('buttons.wiki')}
</a>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard.Root>
<div className="subInfo">
<div className="icons">
<WeaponLabelIcon
labelType={Element[props.gridSummon.object.element]}
/>
</div>
<UncapIndicator
type="summon"
ulb={props.gridSummon.object.uncap.ulb || false}
flb={props.gridSummon.object.uncap.flb || false}
special={false}
/>
</div>
</div>
{wikiButton}
</HovercardContent>
</Hovercard>
)
}

View file

@ -80,7 +80,7 @@ const SummonUnit = ({
})
// Methods: Open layer
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
function openSearchModal() {
if (editable) setSearchModalOpen(true)
}
@ -223,8 +223,8 @@ const SummonUnit = ({
}
// Methods: Core element rendering
const image = (
<div className="SummonImage" onClick={openSearchModal}>
const image = () => {
let image = (
<img
alt={summon?.name[locale]}
className={classNames({
@ -233,21 +233,35 @@ const SummonUnit = ({
})}
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
/>
{editable ? (
<span className="icon">
<PlusIcon />
</span>
) : (
''
)}
</div>
)
)
const content = (
<div className="SummonImage" onClick={openSearchModal}>
{image}
{editable ? (
<span className="icon">
<PlusIcon />
</span>
) : (
''
)}
</div>
)
return gridSummon ? (
<SummonHovercard gridSummon={gridSummon} onTriggerClick={openSearchModal}>
{content}
</SummonHovercard>
) : (
content
)
}
const unitContent = (
<>
<div className={classes}>
{contextMenu()}
{image}
{image()}
{gridSummon ? (
<UncapIndicator
type="summon"
@ -271,11 +285,7 @@ const SummonUnit = ({
</>
)
const unitContentWithHovercard = (
<SummonHovercard gridSummon={gridSummon!}>{unitContent}</SummonHovercard>
)
return gridSummon && !editable ? unitContentWithHovercard : unitContent
return unitContent
}
export default SummonUnit

View file

@ -1,9 +1,9 @@
.Weapon.Hovercard {
.Weapon.HovercardContent {
.skills {
display: flex;
flex-direction: row;
justify-content: space-between;
padding-right: $unit * 2;
padding-right: $unit-2x;
.axSkill {
align-items: center;
@ -36,6 +36,26 @@
display: flex;
flex-direction: column;
font-size: $normal;
gap: calc($unit / 2);
gap: $unit-half;
}
.awakening {
display: flex;
flex-direction: column;
gap: $unit-half;
& > div {
align-items: center;
display: flex;
gap: $unit-half;
img {
width: $unit-4x;
}
strong {
font-weight: $bold;
}
}
}
}

View file

@ -2,18 +2,24 @@ import React from 'react'
import { useRouter } from 'next/router'
import { useTranslation } from 'next-i18next'
import * as HoverCard from '@radix-ui/react-hover-card'
import {
Hovercard,
HovercardContent,
HovercardTrigger,
} from '~components/Hovercard'
import Button from '~components/Button'
import WeaponLabelIcon from '~components/WeaponLabelIcon'
import UncapIndicator from '~components/UncapIndicator'
import ax from '~data/ax'
import { weaponAwakening } from '~data/awakening'
import './index.scss'
interface Props {
gridWeapon: GridWeapon
children: React.ReactNode
onTriggerClick: () => void
}
interface KeyNames {
@ -26,10 +32,11 @@ interface KeyNames {
const WeaponHovercard = (props: Props) => {
const router = useRouter()
const { t } = useTranslation('common')
const locale =
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
const { t } = useTranslation('common')
const Element = ['null', 'wind', 'fire', 'water', 'earth', 'dark', 'light']
const Proficiency = [
'none',
@ -67,11 +74,19 @@ const WeaponHovercard = (props: Props) => {
props.gridWeapon.object.element == 0 && props.gridWeapon.element
? Element[props.gridWeapon.element]
: Element[props.gridWeapon.object.element]
const wikiUrl = `https://gbf.wiki/${props.gridWeapon.object.name.en.replaceAll(
' ',
'_'
)}`
function goTo() {
const urlSafeName = props.gridWeapon.object.name.en.replaceAll(' ', '_')
const url = `https://gbf.wiki/${urlSafeName}`
window.open(url, '_blank')
}
const hovercardSide = () => {
if (props.gridWeapon.position == -1) return 'right'
else if ([6, 7, 8, 9, 10, 11].includes(props.gridWeapon.position))
@ -129,6 +144,33 @@ const WeaponHovercard = (props: Props) => {
return `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/weapon-grid/${weapon.granblue_id}.jpg`
}
const awakeningSection = () => {
const gridAwakening = props.gridWeapon.awakening
const awakening = weaponAwakening.find(
(awakening) => awakening.id === gridAwakening?.type
)
if (gridAwakening && awakening) {
return (
<section className="awakening">
<h5 className={tintElement}>
{t('modals.weapon.subtitles.awakening')}
</h5>
<div>
<img
alt={awakening.name[locale]}
src={`${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/awakening/weapon_${gridAwakening.type}.png`}
/>
<span>
<strong>{`${awakening.name[locale]}`}</strong>&nbsp;
{`Lv${gridAwakening.level}`}
</span>
</div>
</section>
)
}
}
const keysSection = (
<section className="weaponKeys">
{WeaponKeyNames[props.gridWeapon.object.series] ? (
@ -188,65 +230,71 @@ const WeaponHovercard = (props: Props) => {
</section>
)
return (
<HoverCard.Root>
<HoverCard.Trigger>{props.children}</HoverCard.Trigger>
<HoverCard.Portal>
<HoverCard.Content className="Weapon Hovercard" side={hovercardSide()}>
<div className="top">
<div className="title">
<h4>{props.gridWeapon.object.name[locale]}</h4>
<img
alt={props.gridWeapon.object.name[locale]}
src={weaponImage()}
/>
</div>
<div className="subInfo">
<div className="icons">
{props.gridWeapon.object.element !== 0 ||
(props.gridWeapon.object.element === 0 &&
props.gridWeapon.element != null) ? (
<WeaponLabelIcon
labelType={
props.gridWeapon.object.element === 0 &&
props.gridWeapon.element !== 0
? Element[props.gridWeapon.element]
: Element[props.gridWeapon.object.element]
}
/>
) : (
''
)}
<WeaponLabelIcon
labelType={Proficiency[props.gridWeapon.object.proficiency]}
/>
</div>
<UncapIndicator
type="weapon"
ulb={props.gridWeapon.object.uncap.ulb || false}
flb={props.gridWeapon.object.uncap.flb || false}
special={false}
/>
</div>
</div>
const wikiButton = (
<Button
className={tintElement}
text={t('buttons.wiki')}
onClick={goTo}
contained={true}
/>
)
{props.gridWeapon.object.ax &&
props.gridWeapon.ax &&
props.gridWeapon.ax[0].modifier &&
props.gridWeapon.ax[0].strength
? axSection
: ''}
{props.gridWeapon.weapon_keys &&
props.gridWeapon.weapon_keys.length > 0
? keysSection
: ''}
<a className={`Button ${tintElement}`} href={wikiUrl} target="_new">
{t('buttons.wiki')}
</a>
<HoverCard.Arrow />
</HoverCard.Content>
</HoverCard.Portal>
</HoverCard.Root>
return (
<Hovercard openDelay={350}>
<HovercardTrigger asChild onClick={props.onTriggerClick}>
{props.children}
</HovercardTrigger>
<HovercardContent className="Weapon" side={hovercardSide()}>
<div className="top">
<div className="title">
<h4>{props.gridWeapon.object.name[locale]}</h4>
<img
alt={props.gridWeapon.object.name[locale]}
src={weaponImage()}
/>
</div>
<div className="subInfo">
<div className="icons">
{props.gridWeapon.object.element !== 0 ||
(props.gridWeapon.object.element === 0 &&
props.gridWeapon.element != null) ? (
<WeaponLabelIcon
labelType={
props.gridWeapon.object.element === 0 &&
props.gridWeapon.element !== 0
? Element[props.gridWeapon.element]
: Element[props.gridWeapon.object.element]
}
/>
) : (
''
)}
<WeaponLabelIcon
labelType={Proficiency[props.gridWeapon.object.proficiency]}
/>
</div>
<UncapIndicator
type="weapon"
ulb={props.gridWeapon.object.uncap.ulb || false}
flb={props.gridWeapon.object.uncap.flb || false}
special={false}
/>
</div>
</div>
{props.gridWeapon.object.ax &&
props.gridWeapon.ax &&
props.gridWeapon.ax[0].modifier &&
props.gridWeapon.ax[0].strength
? axSection
: ''}
{awakeningSection()}
{props.gridWeapon.weapon_keys && props.gridWeapon.weapon_keys.length > 0
? keysSection
: ''}
{wikiButton}
</HovercardContent>
</Hovercard>
)
}

View file

@ -189,6 +189,7 @@ const WeaponModal = ({
if (gridWeapon.mainhand) appState.grid.weapons.mainWeapon = gridWeapon
else appState.grid.weapons.allWeapons[gridWeapon.position] = gridWeapon
if (onOpenChange) onOpenChange(false)
setOpen(false)
}

View file

@ -98,7 +98,7 @@ const WeaponUnit = ({
setDetailsModalOpen(true)
}
function openSearchModal(event: MouseEvent<HTMLDivElement>) {
function openSearchModal() {
if (editable) setSearchModalOpen(true)
}
@ -509,17 +509,8 @@ const WeaponUnit = ({
}
// Methods: Core element rendering
const image = (
<div className="WeaponImage" onClick={openSearchModal}>
<div className="Modifiers">
{awakeningImage()}
<div className="Skills">
{axImages()}
{telumaImages()}
{opusImages()}
{ultimaImages()}
</div>
</div>
const image = () => {
const image = (
<img
alt={weapon?.name[locale]}
className={classNames({
@ -528,21 +519,44 @@ const WeaponUnit = ({
})}
src={imageUrl !== '' ? imageUrl : placeholderImageUrl()}
/>
{editable ? (
<span className="icon">
<PlusIcon />
</span>
) : (
''
)}
</div>
)
)
const content = (
<div className="WeaponImage" onClick={openSearchModal}>
<div className="Modifiers">
{awakeningImage()}
<div className="Skills">
{axImages()}
{telumaImages()}
{opusImages()}
{ultimaImages()}
</div>
</div>
{image}
{editable ? (
<span className="icon">
<PlusIcon />
</span>
) : (
''
)}
</div>
)
return gridWeapon ? (
<WeaponHovercard gridWeapon={gridWeapon} onTriggerClick={openSearchModal}>
{content}
</WeaponHovercard>
) : (
content
)
}
const unitContent = (
<>
<div className={classes}>
{contextMenu()}
{image}
{image()}
{gridWeapon && weapon ? (
<UncapIndicator
type="weapon"
@ -562,11 +576,7 @@ const WeaponUnit = ({
</>
)
const unitContentWithHovercard = (
<WeaponHovercard gridWeapon={gridWeapon!}>{unitContent}</WeaponHovercard>
)
return gridWeapon && !editable ? unitContentWithHovercard : unitContent
return unitContent
}
export default WeaponUnit

View file

@ -330,3 +330,54 @@ export const aetherialMastery: ItemSkill[] = [
fractional: false,
},
]
export const permanentMastery: ItemSkill[] = [
{
name: {
en: 'Extended Mastery Star Cap',
ja: 'LB強化回数上限',
},
id: 1,
slug: 'star-cap',
minValue: 10,
maxValue: 10,
suffix: '',
fractional: false,
},
{
name: {
en: 'ATK',
ja: '攻撃',
},
id: 2,
slug: 'atk',
minValue: 10,
maxValue: 10,
suffix: '%',
fractional: false,
},
{
name: {
en: 'HP',
ja: 'HP',
},
id: 3,
slug: 'hp',
minValue: 10,
maxValue: 10,
suffix: '',
fractional: false,
},
{
name: {
en: 'DMG Cap',
ja: 'ダメージ上限',
},
id: 4,
slug: 'dmg-cap',
minValue: 5,
maxValue: 5,
suffix: '%',
fractional: false,
},
]

View file

@ -200,98 +200,6 @@ select {
}
}
.Hovercard {
background: #222;
border-radius: $unit;
color: $grey-100;
display: flex;
flex-direction: column;
gap: $unit * 2;
padding: $unit * 2;
width: 300px;
.top {
display: flex;
flex-direction: column;
gap: calc($unit / 2);
.title {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit * 2;
h4 {
flex-grow: 1;
font-size: $font-medium;
line-height: 1.2;
min-width: 140px;
}
img {
height: auto;
width: 100px;
}
}
.subInfo {
align-items: center;
display: flex;
flex-direction: row;
gap: $unit * 2;
.icons {
display: flex;
flex-direction: row;
flex-grow: 1;
gap: $unit;
}
.UncapIndicator {
width: 100px;
}
}
}
section {
h5 {
font-size: $font-small;
font-weight: $medium;
opacity: 0.7;
&.wind {
color: $wind-bg-20;
}
&.fire {
color: $fire-bg-20;
}
&.water {
color: $water-bg-20;
}
&.earth {
color: $earth-bg-20;
}
&.dark {
color: $dark-bg-10;
}
&.light {
color: $light-bg-20;
}
}
}
a.Button {
display: block;
padding: $unit * 1.5;
text-align: center;
}
}
#Teams,
#Profile {
display: flex;
@ -447,17 +355,25 @@ i.tag {
opacity: 0.6;
transform: scale(1);
}
70% {
opacity: 0.8;
65% {
opacity: 0.65;
transform: scale(1.1);
}
70% {
opacity: 0.7;
transform: scale(1);
}
75% {
opacity: 0.75;
transform: scale(0.98);
}
80% {
opacity: 0.8;
transform: scale(1);
transform: scale(1.02);
}
90% {
opacity: 0.8;
transform: scale(0.95);
opacity: 0.9;
transform: scale(0.96);
}
100% {
opacity: 1;