diff --git a/components/party/PartyFooter/index.tsx b/components/party/PartyFooter/index.tsx index 9ae4e432..a028a0d1 100644 --- a/components/party/PartyFooter/index.tsx +++ b/components/party/PartyFooter/index.tsx @@ -260,16 +260,7 @@ const PartyFooter = (props: Props) => { return partySnapshot?.remixes.map((party, i) => { return ( .weaponGrid { + .gridContainer { + aspect-ratio: 2/0.95; + width: 100%; + } + + .characterGrid { + aspect-ratio: 2/0.95; + display: flex; + justify-content: space-between; + + .protagonist { + border-width: 1px; + border-style: solid; + + &.fire { + background: var(--fire-portrait-bg); + border-color: var(--fire-bg); + } + + &.water { + background: var(--water-portrait-bg); + border-color: var(--water-bg); + } + + &.wind { + background: var(--wind-portrait-bg); + border-color: var(--wind-bg); + } + + &.earth { + background: var(--earth-portrait-bg); + border-color: var(--earth-bg); + } + + &.light { + background: var(--light-portrait-bg); + border-color: var(--light-bg); + } + + &.dark { + background: var(--dark-portrait-bg); + border-color: var(--dark-bg); + } + + &.empty { + background: var(--card-bg); + } + } + + .grid { + background: var(--background); + border-radius: $item-corner-small; + aspect-ratio: 69/142; + list-style: none; + height: calc(100% - $unit-half); + + img { + border-radius: $item-corner-small; + width: 100%; + } + } + } + + .weaponGrid { aspect-ratio: 2/0.95; display: grid; grid-template-columns: 1fr 3.36fr; /* left column takes up 1 fraction, right column takes up 3 fractions */ @@ -54,7 +125,7 @@ .weapon { background: var(--unit-bg); - border-radius: 4px; + border-radius: $item-corner-small; } .mainhand.weapon { @@ -91,6 +162,51 @@ } } + .summonGrid { + aspect-ratio: 2/0.94; + display: flex; + gap: $unit; + justify-content: space-between; + + .summon, + .mainSummon { + background: var(--background); + border-radius: $item-corner-small; + + img { + border-radius: $item-corner-small; + width: 100%; + } + } + + .mainSummon { + aspect-ratio: 56/97; + display: grid; + grid-column: 1 / 2; /* spans one column */ + } + + .summons { + display: grid; /* make the right-images container a grid */ + grid-template-columns: repeat( + 2, + 1fr + ); /* create 3 columns, each taking up 1 fraction */ + grid-template-rows: repeat( + 2, + 1fr + ); /* create 3 rows, each taking up 1 fraction */ + gap: $unit; + aspect-ratio: 83/100; + // column-gap: $unit; + // row-gap: $unit-2x; + } + + .summon { + aspect-ratio: 184 / 138; + display: grid; + } + } + .details { display: flex; flex-direction: column; @@ -104,6 +220,7 @@ padding-bottom: 1px; text-overflow: ellipsis; white-space: nowrap; + min-height: 24px; max-width: 258px; // Can we not do this? &.empty { @@ -157,6 +274,7 @@ } time { + line-height: 18px; white-space: nowrap; } @@ -234,4 +352,41 @@ } } } + + .indicators { + display: flex; + flex-direction: row; + gap: $unit; + margin-top: $unit * -1; + margin-bottom: $unit-fourth; + justify-content: center; + opacity: 0; + + @include breakpoint(phone) { + display: none; + } + + li { + flex-grow: 1; + text-indent: -9999px; + padding: $unit 0; + + .indicator { + transition: background-color 0.12s ease-in-out; + height: $unit; + border-radius: $unit-half; + background-color: var(--button-contained-bg-hover); + } + + span { + text-indent: -9999px; + position: absolute; + } + + &:hover .indicator, + &.active .indicator { + background-color: var(--text-secondary); + } + } + } } diff --git a/components/reps/GridRep/index.tsx b/components/reps/GridRep/index.tsx index 84e9b86e..7f27892c 100644 --- a/components/reps/GridRep/index.tsx +++ b/components/reps/GridRep/index.tsx @@ -16,23 +16,15 @@ import ShieldIcon from '~public/icons/Shield.svg' import styles from './index.module.scss' interface Props { - shortcode: string - id: string - name: string - raid: Raid - grid: GridWeapon[] - user?: User - fullAuto: boolean - autoGuard: boolean - favorited: boolean + party: Party loading: boolean - createdAt: Date onClick: (shortcode: string) => void onSave?: (partyId: string, favorited: boolean) => void } -const GridRep = (props: Props) => { +const GridRep = ({ party, loading, onClick, onSave }: Props) => { const numWeapons: number = 9 + const numSummons: number = 6 const { account } = useSnapshot(accountState) @@ -42,27 +34,42 @@ const GridRep = (props: Props) => { router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en' const [visible, setVisible] = useState(false) + const [currentView, setCurrentView] = useState< + 'characters' | 'weapons' | 'summons' + >('weapons') + const [mainhand, setMainhand] = useState() const [weapons, setWeapons] = useState>({}) - const [grid, setGrid] = useState>({}) + const [weaponGrid, setWeaponGrid] = useState>({}) + const [characters, setCharacters] = useState>({}) + const [characterGrid, setCharacterGrid] = useState>( + {} + ) + const [mainSummon, setMainSummon] = useState() + const [friendSummon, setFriendSummon] = useState() + const [summons, setSummons] = useState>({}) + const [summonGrid, setSummonGrid] = useState>({}) - const gridRepStyles = classNames({ + // Style construction + + const gridRepClasses = classNames({ [styles.gridRep]: true, [styles.visible]: visible, [styles.hidden]: !visible, }) + const titleClass = classNames({ - empty: !props.name, + empty: !party.name, }) const raidClass = classNames({ [styles.raid]: true, - [styles.empty]: !props.raid, + [styles.empty]: !party.raid, }) const userClass = classNames({ [styles.user]: true, - [styles.empty]: !props.user, + [styles.empty]: !party.user, }) const mainhandClasses = classNames({ @@ -75,8 +82,22 @@ const GridRep = (props: Props) => { [styles.grid]: true, }) + const protagonistClasses = classNames({ + [styles.protagonist]: true, + [styles.grid]: true, + [styles[`${numberToElement()}`]]: true, + [styles.empty]: !party.job || party.job.id === '-1', + }) + + const characterClasses = classNames({ + [styles.character]: true, + [styles.grid]: true, + }) + + // Hooks + useEffect(() => { - if (props.loading) { + if (loading) { setVisible(false) } else { const timeout = setTimeout(() => { @@ -84,7 +105,7 @@ const GridRep = (props: Props) => { }, 150) return () => clearTimeout(timeout) } - }, [props.loading]) + }, [loading]) useEffect(() => { setVisible(false) // Trigger fade out @@ -99,7 +120,7 @@ const GridRep = (props: Props) => { const gridWeapons = Array(numWeapons) let foundMainhand = false - for (const [key, value] of Object.entries(props.grid)) { + for (const [key, value] of Object.entries(party.weapons)) { if (value.position == -1) { setMainhand(value.object) foundMainhand = true @@ -114,18 +135,74 @@ const GridRep = (props: Props) => { } setWeapons(newWeapons) - setGrid(gridWeapons) - }, [props.grid]) + setWeaponGrid(gridWeapons) + }, [party]) - function navigate() { - props.onClick(props.shortcode) + useEffect(() => { + const newCharacters = Array(3) + const gridCharacters = Array(3) + + if (party.characters) { + for (const [key, value] of Object.entries(party.characters)) { + if (value.position != null) { + newCharacters[value.position] = value.object + gridCharacters[value.position] = value + } + } + + setCharacters(newCharacters) + setCharacterGrid(gridCharacters) + } + }, [party]) + + useEffect(() => { + const newSummons = Array(numSummons) + const gridSummons = Array(numSummons) + + if (party.summons) { + for (const [key, value] of Object.entries(party.summons)) { + if (value.main) { + setMainSummon(value) + } else if (value.friend) { + setFriendSummon(value) + } else if (!value.main && !value.friend && value.position != null) { + newSummons[value.position] = value.object + gridSummons[value.position] = value + } + } + + setSummons(newSummons) + setSummonGrid(gridSummons) + } + }, [party]) + + // Convert element to string + function numberToElement() { + switch (mainhand?.element || weaponGrid[0]?.element) { + case 1: + return 'wind' + case 2: + return 'fire' + case 3: + return 'water' + case 4: + return 'earth' + case 5: + return 'dark' + case 6: + return 'light' + default: + return '' + } } + // Methods: Image generation + function generateMainhandImage() { let url = '' if (mainhand) { - const weapon = Object.values(props.grid).find( + const weapon = Object.values(party.weapons).find( (w) => w && w.object.id === mainhand.id ) @@ -136,18 +213,18 @@ const GridRep = (props: Props) => { } } - return mainhand && props.grid[0] ? ( + return mainhand && party.weapons[0] ? ( {mainhand.name[locale]} ) : ( '' ) } - function generateGridImage(position: number) { + function generateWeaponGridImage(position: number) { let url = '' const weapon = weapons[position] - const gridWeapon = grid[position] + const gridWeapon = weaponGrid[position] if (weapon && gridWeapon) { if (weapon.element == 0 && gridWeapon.element) { @@ -164,19 +241,163 @@ const GridRep = (props: Props) => { ) } + function generateMCImage() { + let source = '' + + if (party.job) { + const slug = party.job.name.en.replaceAll(' ', '-').toLowerCase() + const gender = party.user?.gender == 1 ? 'b' : 'a' + source = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-portraits/${slug}_${gender}.png` + } + + return ( + party.job && + party.job.id !== '-1' && ( + {party.job + ) + ) + } + + function generateCharacterGridImage(position: number) { + let url = '' + + const gridCharacter = characterGrid[position] + const character = characters[position] + + if (character && gridCharacter) { + // Change the image based on the uncap level + let suffix = '01' + if (gridCharacter.transcendence_step > 0) suffix = '04' + else if (gridCharacter.uncap_level >= 5) suffix = '03' + else if (gridCharacter.uncap_level > 2) suffix = '02' + + if (gridCharacter.object.granblue_id === '3030182000') { + let element = 1 + if (mainhand && mainhand.element) { + element = mainhand.element + } + + suffix = `${suffix}_0${element}` + } + + const url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg` + + return ( + characters[position] && ( + {characters[position]?.name[locale]} + ) + ) + } + } + + function generateMainSummonImage(position: 'main' | 'friend') { + let url = '' + + const upgradedSummons = [ + '2040094000', + '2040100000', + '2040080000', + '2040098000', + '2040090000', + '2040084000', + '2040003000', + '2040056000', + '2040020000', + '2040034000', + '2040028000', + '2040027000', + '2040046000', + '2040047000', + ] + + const summon = position === 'main' ? mainSummon : friendSummon + + if (summon) { + // Change the image based on the uncap level + let suffix = '' + if (summon.object.uncap.xlb && summon.uncap_level == 6) { + if (summon.transcendence_step >= 1 && summon.transcendence_step < 5) { + suffix = '_03' + } else if (summon.transcendence_step === 5) { + suffix = '_04' + } + } else if ( + upgradedSummons.indexOf(summon.object.granblue_id.toString()) != -1 && + summon.uncap_level == 5 + ) { + suffix = '_02' + } + + url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.object.granblue_id}${suffix}.jpg` + } + + return summon && {summon.object.name[locale]} + } + + function generateSummonGridImage(position: number) { + let url = '' + + const gridSummon = summonGrid[position] + const summon = gridSummon?.object + + const upgradedSummons = [ + '2040094000', + '2040100000', + '2040080000', + '2040098000', + '2040090000', + '2040084000', + '2040003000', + '2040056000', + '2040020000', + '2040034000', + '2040028000', + '2040027000', + '2040046000', + '2040047000', + ] + + if (summon && gridSummon) { + // Change the image based on the uncap level + let suffix = '' + if (gridSummon.object.uncap.xlb && gridSummon.uncap_level == 6) { + if ( + gridSummon.transcendence_step >= 1 && + gridSummon.transcendence_step < 5 + ) { + suffix = '_03' + } else if (gridSummon.transcendence_step === 5) { + suffix = '_04' + } + } else if ( + upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 && + gridSummon.uncap_level == 5 + ) { + suffix = '_02' + } + + url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg` + } + return ( + summons[position] && ( + {summons[position]?.name[locale]} + ) + ) + } + function sendSaveData() { - if (props.onSave) props.onSave(props.id, props.favorited) + if (onSave) onSave(party.id, party.favorited) } const userImage = () => { - if (props.user && props.user.avatar) { + if (party.user && party.user.avatar) { return ( {props.user.avatar.picture} ) } else @@ -194,63 +415,95 @@ const GridRep = (props: Props) => { const attribution = () => ( {userImage()} - {props.user ? props.user.username : t('no_user')} + {party.user ? party.user.username : t('no_user')} ) - function fullAutoString() { - const fullAutoElement = ( - - {` · ${t('party.details.labels.full_auto')}`} - - ) + const renderWeaponGrid = ( +
+
{generateMainhandImage()}
- const autoGuardElement = ( - - - - ) +
    + {Array.from(Array(numWeapons)).map((x, i) => { + return ( +
  • + {generateWeaponGridImage(i)} +
  • + ) + })} +
+
+ ) - return ( -
- {fullAutoElement} - {props.autoGuard ? autoGuardElement : ''} + const renderCharacterGrid = ( +
+
{generateMCImage()}
+ {Array.from(Array(3)).map((x, i) => { + return ( +
  • + {generateCharacterGridImage(i)} +
  • + ) + })} +
    + ) + + const renderSummonGrid = ( +
    +
    {generateMainSummonImage('main')}
    +
      + {Array.from(Array(numSummons)).map((x, i) => { + return ( +
    • + {generateSummonGridImage(i)} +
    • + ) + })} +
    +
    + {generateMainSummonImage('friend')}
    - ) - } +
    + ) const detailsWithUsername = (

    - {props.name ? props.name : t('no_title')} + {party.name ? party.name : t('no_title')}

    - {props.raid ? props.raid.name[locale] : t('no_raid')} + {party.raid ? party.raid.name[locale] : t('no_raid')} - {props.fullAuto && ( + {party.full_auto && ( {` · ${t('party.details.labels.full_auto')}`} )} - {props.raid && props.raid.group.extra && ( + {party.raid && party.raid.group.extra && ( {` · EX`} )}
    {account.authorized && - ((props.user && account.user && account.user.id !== props.user.id) || - !props.user) ? ( + ((party.user && account.user && account.user.id !== party.user.id) || + !party.user) ? (
    ) - return ( - - - {detailsWithUsername} -
    -
    {generateMainhandImage()}
    + function changeView(view: 'characters' | 'weapons' | 'summons') { + setCurrentView(view) + } -
      - {Array.from(Array(numWeapons)).map((x, i) => { - return ( -
    • - {generateGridImage(i)} -
    • - ) - })} -
    -
    -
    + return ( + changeView('weapons')} + > + {detailsWithUsername} +
    + {currentView === 'characters' + ? renderCharacterGrid + : currentView === 'summons' + ? renderSummonGrid + : renderWeaponGrid} +
    +
      +
    • changeView('characters')} + > +
      + Characters +
    • +
    • changeView('weapons')} + > +
      + Weapons +
    • +
    • changeView('summons')} + > +
      + Summons +
    • +
    ) } diff --git a/pages/[username].tsx b/pages/[username].tsx index 1b7a976c..697abbdf 100644 --- a/pages/[username].tsx +++ b/pages/[username].tsx @@ -255,16 +255,7 @@ const ProfileRoute: React.FC = ({ return parties.map((party, i) => { return ( = ({ return parties.map((party, i) => { return ( = ({ return parties.map((party, i) => { return (