Merge pull request #192 from jedmund/fix-181
Ensure header title updates when party name is updated
This commit is contained in:
commit
5dd81d377d
5 changed files with 340 additions and 269 deletions
|
|
@ -4,10 +4,9 @@
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: 0 1px 4px rgb(0 0 0 / 8%);
|
box-shadow: 0 1px 4px rgb(0 0 0 / 8%);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-width: 30vw;
|
width: 30vw;
|
||||||
|
max-width: 180px;
|
||||||
margin: 0 $unit-2x;
|
margin: 0 $unit-2x;
|
||||||
// top: $unit-8x; // This shouldn't be hardcoded. How to calculate it?
|
|
||||||
// Also, add space that doesn't make the menu disappear if you move your mouse slowly
|
|
||||||
z-index: 15;
|
z-index: 15;
|
||||||
|
|
||||||
@include breakpoint(phone) {
|
@include breakpoint(phone) {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { useSnapshot } from 'valtio'
|
import { subscribe, useSnapshot } from 'valtio'
|
||||||
|
import { subscribeKey } from 'valtio/utils'
|
||||||
import { deleteCookie } from 'cookies-next'
|
import { deleteCookie } from 'cookies-next'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { Trans, useTranslation } from 'next-i18next'
|
import { Trans, useTranslation } from 'next-i18next'
|
||||||
|
|
@ -9,7 +10,7 @@ import Link from 'next/link'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { accountState, initialAccountState } from '~utils/accountState'
|
import { accountState, initialAccountState } from '~utils/accountState'
|
||||||
import { appState } from '~utils/appState'
|
import { appState, initialAppState } from '~utils/appState'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
|
|
@ -49,28 +50,25 @@ const Header = () => {
|
||||||
const [settingsModalOpen, setSettingsModalOpen] = useState(false)
|
const [settingsModalOpen, setSettingsModalOpen] = useState(false)
|
||||||
const [leftMenuOpen, setLeftMenuOpen] = useState(false)
|
const [leftMenuOpen, setLeftMenuOpen] = useState(false)
|
||||||
const [rightMenuOpen, setRightMenuOpen] = useState(false)
|
const [rightMenuOpen, setRightMenuOpen] = useState(false)
|
||||||
|
|
||||||
|
const [name, setName] = useState('')
|
||||||
const [originalName, setOriginalName] = useState('')
|
const [originalName, setOriginalName] = useState('')
|
||||||
|
|
||||||
// Snapshots
|
// Snapshots
|
||||||
const { account } = useSnapshot(accountState)
|
const { account } = useSnapshot(accountState)
|
||||||
const { party } = useSnapshot(appState)
|
const { party: partySnapshot } = useSnapshot(appState)
|
||||||
|
|
||||||
function handleCopyToastOpenChanged(open: boolean) {
|
// Subscribe to app state to listen for party name and
|
||||||
setCopyToastOpen(open)
|
// unsubscribe when component is unmounted
|
||||||
}
|
const unsubscribe = subscribe(appState, () => {
|
||||||
|
const newName =
|
||||||
|
appState.party && appState.party.name ? appState.party.name : ''
|
||||||
|
setName(newName)
|
||||||
|
})
|
||||||
|
|
||||||
function handleCopyToastCloseClicked() {
|
useEffect(() => () => unsubscribe(), [])
|
||||||
setCopyToastOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRemixToastOpenChanged(open: boolean) {
|
|
||||||
setRemixToastOpen(open)
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleRemixToastCloseClicked() {
|
|
||||||
setRemixToastOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Methods: Event handlers (Buttons)
|
||||||
function handleLeftMenuButtonClicked() {
|
function handleLeftMenuButtonClicked() {
|
||||||
setLeftMenuOpen(!leftMenuOpen)
|
setLeftMenuOpen(!leftMenuOpen)
|
||||||
}
|
}
|
||||||
|
|
@ -79,9 +77,11 @@ const Header = () => {
|
||||||
setRightMenuOpen(!rightMenuOpen)
|
setRightMenuOpen(!rightMenuOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Methods: Event handlers (Menus)
|
||||||
function handleLeftMenuOpenChange(open: boolean) {
|
function handleLeftMenuOpenChange(open: boolean) {
|
||||||
setLeftMenuOpen(open)
|
setLeftMenuOpen(open)
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleRightMenuOpenChange(open: boolean) {
|
function handleRightMenuOpenChange(open: boolean) {
|
||||||
setRightMenuOpen(open)
|
setRightMenuOpen(open)
|
||||||
}
|
}
|
||||||
|
|
@ -94,6 +94,41 @@ const Header = () => {
|
||||||
setRightMenuOpen(false)
|
setRightMenuOpen(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Methods: Event handlers (Copy toast)
|
||||||
|
function handleCopyToastOpenChanged(open: boolean) {
|
||||||
|
setCopyToastOpen(open)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCopyToastCloseClicked() {
|
||||||
|
setCopyToastOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Event handlers (Remix toasts)
|
||||||
|
function handleRemixToastOpenChanged(open: boolean) {
|
||||||
|
setRemixToastOpen(open)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRemixToastCloseClicked() {
|
||||||
|
setRemixToastOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods: Actions
|
||||||
|
function handleNewParty(event: React.MouseEvent, path: string) {
|
||||||
|
event.preventDefault()
|
||||||
|
|
||||||
|
// Clean state
|
||||||
|
const resetState = clonedeep(initialAppState)
|
||||||
|
Object.keys(resetState).forEach((key) => {
|
||||||
|
appState[key] = resetState[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Push the root URL
|
||||||
|
router.push(path)
|
||||||
|
|
||||||
|
// Close right menu
|
||||||
|
closeRightMenu()
|
||||||
|
}
|
||||||
|
|
||||||
function copyToClipboard() {
|
function copyToClipboard() {
|
||||||
const path = router.asPath.split('/')[1]
|
const path = router.asPath.split('/')[1]
|
||||||
|
|
||||||
|
|
@ -111,16 +146,6 @@ const Header = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleNewParty(event: React.MouseEvent, path: string) {
|
|
||||||
event.preventDefault()
|
|
||||||
|
|
||||||
// Push the root URL
|
|
||||||
router.push(path)
|
|
||||||
|
|
||||||
// Close right menu
|
|
||||||
closeRightMenu()
|
|
||||||
}
|
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
// Close menu
|
// Close menu
|
||||||
closeRightMenu()
|
closeRightMenu()
|
||||||
|
|
@ -139,38 +164,39 @@ const Header = () => {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleFavorite() {
|
|
||||||
if (party.favorited) unsaveFavorite()
|
|
||||||
else saveFavorite()
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFavorite() {
|
|
||||||
if (party.id)
|
|
||||||
api.saveTeam({ id: party.id }).then((response) => {
|
|
||||||
if (response.status == 201) appState.party.favorited = true
|
|
||||||
})
|
|
||||||
else console.error('Failed to save team: No party ID')
|
|
||||||
}
|
|
||||||
|
|
||||||
function unsaveFavorite() {
|
|
||||||
if (party.id)
|
|
||||||
api.unsaveTeam({ id: party.id }).then((response) => {
|
|
||||||
if (response.status == 200) appState.party.favorited = false
|
|
||||||
})
|
|
||||||
else console.error('Failed to unsave team: No party ID')
|
|
||||||
}
|
|
||||||
|
|
||||||
function remixTeam() {
|
function remixTeam() {
|
||||||
setOriginalName(party.name ? party.name : t('no_title'))
|
setOriginalName(partySnapshot.name ? partySnapshot.name : t('no_title'))
|
||||||
|
|
||||||
if (party.shortcode)
|
if (partySnapshot.shortcode)
|
||||||
api.remix(party.shortcode).then((response) => {
|
api.remix(partySnapshot.shortcode).then((response) => {
|
||||||
const remix = response.data.party
|
const remix = response.data.party
|
||||||
router.push(`/p/${remix.shortcode}`)
|
router.push(`/p/${remix.shortcode}`)
|
||||||
setRemixToastOpen(true)
|
setRemixToastOpen(true)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function toggleFavorite() {
|
||||||
|
if (partySnapshot.favorited) unsaveFavorite()
|
||||||
|
else saveFavorite()
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveFavorite() {
|
||||||
|
if (partySnapshot.id)
|
||||||
|
api.saveTeam({ id: partySnapshot.id }).then((response) => {
|
||||||
|
if (response.status == 201) appState.party.favorited = true
|
||||||
|
})
|
||||||
|
else console.error('Failed to save team: No party ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
function unsaveFavorite() {
|
||||||
|
if (partySnapshot.id)
|
||||||
|
api.unsaveTeam({ id: partySnapshot.id }).then((response) => {
|
||||||
|
if (response.status == 200) appState.party.favorited = false
|
||||||
|
})
|
||||||
|
else console.error('Failed to unsave team: No party ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendering: Elements
|
||||||
const pageTitle = () => {
|
const pageTitle = () => {
|
||||||
let title = ''
|
let title = ''
|
||||||
let hasAccessory = false
|
let hasAccessory = false
|
||||||
|
|
@ -226,6 +252,41 @@ const Header = () => {
|
||||||
return image
|
return image
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rendering: Buttons
|
||||||
|
const saveButton = () => {
|
||||||
|
return (
|
||||||
|
<Tooltip content={t('tooltips.save')}>
|
||||||
|
<Button
|
||||||
|
leftAccessoryIcon={<SaveIcon />}
|
||||||
|
className={classNames({
|
||||||
|
Save: true,
|
||||||
|
Saved: partySnapshot.favorited,
|
||||||
|
})}
|
||||||
|
blended={true}
|
||||||
|
text={
|
||||||
|
partySnapshot.favorited ? t('buttons.saved') : t('buttons.save')
|
||||||
|
}
|
||||||
|
onClick={toggleFavorite}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const remixButton = () => {
|
||||||
|
return (
|
||||||
|
<Tooltip content={t('tooltips.remix')}>
|
||||||
|
<Button
|
||||||
|
leftAccessoryIcon={<RemixIcon />}
|
||||||
|
className="Remix"
|
||||||
|
blended={true}
|
||||||
|
text={t('buttons.remix')}
|
||||||
|
onClick={remixTeam}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rendering: Toasts
|
||||||
const urlCopyToast = () => {
|
const urlCopyToast = () => {
|
||||||
return (
|
return (
|
||||||
<Toast
|
<Toast
|
||||||
|
|
@ -258,37 +319,7 @@ const Header = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveButton = () => {
|
// Rendering: Modals
|
||||||
return (
|
|
||||||
<Tooltip content={t('tooltips.save')}>
|
|
||||||
<Button
|
|
||||||
leftAccessoryIcon={<SaveIcon />}
|
|
||||||
className={classNames({
|
|
||||||
Save: true,
|
|
||||||
Saved: party.favorited,
|
|
||||||
})}
|
|
||||||
blended={true}
|
|
||||||
text={party.favorited ? t('buttons.saved') : t('buttons.save')}
|
|
||||||
onClick={toggleFavorite}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const remixButton = () => {
|
|
||||||
return (
|
|
||||||
<Tooltip content={t('tooltips.remix')}>
|
|
||||||
<Button
|
|
||||||
leftAccessoryIcon={<RemixIcon />}
|
|
||||||
className="Remix"
|
|
||||||
blended={true}
|
|
||||||
text={t('buttons.remix')}
|
|
||||||
onClick={remixTeam}
|
|
||||||
/>
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const settingsModal = () => {
|
const settingsModal = () => {
|
||||||
const user = accountState.account.user
|
const user = accountState.account.user
|
||||||
|
|
||||||
|
|
@ -317,6 +348,7 @@ const Header = () => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rendering: Compositing
|
||||||
const left = () => {
|
const left = () => {
|
||||||
return (
|
return (
|
||||||
<section>
|
<section>
|
||||||
|
|
@ -348,7 +380,7 @@ const Header = () => {
|
||||||
<section>
|
<section>
|
||||||
{router.route === '/p/[party]' &&
|
{router.route === '/p/[party]' &&
|
||||||
account.user &&
|
account.user &&
|
||||||
(!party.user || party.user.id !== account.user.id) &&
|
(!partySnapshot.user || partySnapshot.user.id !== account.user.id) &&
|
||||||
!appState.errorCode
|
!appState.errorCode
|
||||||
? saveButton()
|
? saveButton()
|
||||||
: ''}
|
: ''}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
|
|
||||||
&.Visible {
|
&.Visible {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fieldset {
|
fieldset {
|
||||||
|
|
@ -340,6 +339,18 @@
|
||||||
font-size: $font-small;
|
font-size: $font-small;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a:visited:not(.fire):not(.water):not(.wind):not(.earth):not(.dark):not(
|
||||||
|
.light
|
||||||
|
) {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover:not(.fire):not(.water):not(.wind):not(.earth):not(.dark):not(
|
||||||
|
.light
|
||||||
|
) {
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
|
||||||
& > *:not(:last-child):after {
|
& > *:not(:last-child):after {
|
||||||
content: ' · ';
|
content: ' · ';
|
||||||
margin: 0 calc($unit / 2);
|
margin: 0 calc($unit / 2);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
|
import React, { useEffect, useState, ChangeEvent, KeyboardEvent } from 'react'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { useSnapshot } from 'valtio'
|
import { subscribe, useSnapshot } from 'valtio'
|
||||||
import { useTranslation } from 'next-i18next'
|
import { useTranslation } from 'next-i18next'
|
||||||
import clonedeep from 'lodash.clonedeep'
|
import clonedeep from 'lodash.clonedeep'
|
||||||
|
|
||||||
|
|
@ -25,7 +25,7 @@ import Token from '~components/Token'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import { accountState } from '~utils/accountState'
|
import { accountState } from '~utils/accountState'
|
||||||
import { appState } from '~utils/appState'
|
import { appState, initialAppState } from '~utils/appState'
|
||||||
import { formatTimeAgo } from '~utils/timeAgo'
|
import { formatTimeAgo } from '~utils/timeAgo'
|
||||||
import { youtube } from '~utils/youtube'
|
import { youtube } from '~utils/youtube'
|
||||||
|
|
||||||
|
|
@ -124,6 +124,26 @@ const PartyDetails = (props: Props) => {
|
||||||
}
|
}
|
||||||
}, [props.party])
|
}, [props.party])
|
||||||
|
|
||||||
|
// Subscribe to router changes and reset state
|
||||||
|
// if the new route is a new team
|
||||||
|
useEffect(() => {
|
||||||
|
router.events.on('routeChangeStart', (url, { shallow }) => {
|
||||||
|
if (url === '/new' || url === '/') {
|
||||||
|
const party = initialAppState.party
|
||||||
|
|
||||||
|
setName(party.name ? party.name : '')
|
||||||
|
setAutoGuard(party.autoGuard)
|
||||||
|
setFullAuto(party.fullAuto)
|
||||||
|
setChargeAttack(party.chargeAttack)
|
||||||
|
setClearTime(party.clearTime)
|
||||||
|
setRemixes(party.remixes)
|
||||||
|
setTurnCount(party.turnCount)
|
||||||
|
setButtonCount(party.buttonCount)
|
||||||
|
setChainCount(party.chainCount)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Extract the video IDs from the description
|
// Extract the video IDs from the description
|
||||||
if (appState.party.description) {
|
if (appState.party.description) {
|
||||||
|
|
@ -475,7 +495,8 @@ const PartyDetails = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const editable = (
|
const editable = () => {
|
||||||
|
return (
|
||||||
<section className={editableClasses}>
|
<section className={editableClasses}>
|
||||||
<CharLimitedFieldset
|
<CharLimitedFieldset
|
||||||
fieldName="name"
|
fieldName="name"
|
||||||
|
|
@ -630,6 +651,7 @@ const PartyDetails = (props: Props) => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const clearTimeString = () => {
|
const clearTimeString = () => {
|
||||||
const minutes = Math.floor(clearTime / 60)
|
const minutes = Math.floor(clearTime / 60)
|
||||||
|
|
@ -664,21 +686,28 @@ const PartyDetails = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const readOnly = (
|
const readOnly = () => {
|
||||||
|
return (
|
||||||
<section className={readOnlyClasses}>
|
<section className={readOnlyClasses}>
|
||||||
<section className="Details">
|
<section className="Details">
|
||||||
<Token className={classNames({ ChargeAttack: true, On: chargeAttack })}>
|
<Token
|
||||||
|
className={classNames({ ChargeAttack: true, On: chargeAttack })}
|
||||||
|
>
|
||||||
{`${t('party.details.labels.charge_attack')} ${
|
{`${t('party.details.labels.charge_attack')} ${
|
||||||
chargeAttack ? 'On' : 'Off'
|
chargeAttack ? 'On' : 'Off'
|
||||||
}`}
|
}`}
|
||||||
</Token>
|
</Token>
|
||||||
|
|
||||||
<Token className={classNames({ FullAuto: true, On: fullAuto })}>
|
<Token className={classNames({ FullAuto: true, On: fullAuto })}>
|
||||||
{`${t('party.details.labels.full_auto')} ${fullAuto ? 'On' : 'Off'}`}
|
{`${t('party.details.labels.full_auto')} ${
|
||||||
|
fullAuto ? 'On' : 'Off'
|
||||||
|
}`}
|
||||||
</Token>
|
</Token>
|
||||||
|
|
||||||
<Token className={classNames({ AutoGuard: true, On: autoGuard })}>
|
<Token className={classNames({ AutoGuard: true, On: autoGuard })}>
|
||||||
{`${t('party.details.labels.auto_guard')} ${fullAuto ? 'On' : 'Off'}`}
|
{`${t('party.details.labels.auto_guard')} ${
|
||||||
|
fullAuto ? 'On' : 'Off'
|
||||||
|
}`}
|
||||||
</Token>
|
</Token>
|
||||||
|
|
||||||
{turnCount ? (
|
{turnCount ? (
|
||||||
|
|
@ -696,6 +725,7 @@ const PartyDetails = (props: Props) => {
|
||||||
<Linkify>{embeddedDescription}</Linkify>
|
<Linkify>{embeddedDescription}</Linkify>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const remixSection = () => {
|
const remixSection = () => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -755,8 +785,8 @@ const PartyDetails = (props: Props) => {
|
||||||
''
|
''
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{readOnly}
|
{readOnly()}
|
||||||
{editable}
|
{editable()}
|
||||||
|
|
||||||
{deleteAlert()}
|
{deleteAlert()}
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -35,8 +35,7 @@ const NewRoute: React.FC<Props> = ({
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
function callback(path: string) {
|
function callback(path: string) {
|
||||||
// This is scuffed, how do we do this natively?
|
router.push(path, undefined, { shallow: true })
|
||||||
window.history.replaceState(null, `Grid Tool`, `${path}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue