Quality pass (#326)

* Move min-width to RaidCombobox, not Popover

This fixes #318

* Use snapshots to make tokens reactive

This fixes #319

* Revert ChatGPT refactor of this method

Oops. This code was nice, but it didn't actually assign `false` to keys to be sent to the server. We will revisit this, but it needs to be fixed right now.

This fixes #325

* Ignore gacha directory

We will probably scrape these images soon.

* Add translation for Auto Summon token

* Add auto summon token to app state

* Set battle settings in state on update

Also renames PartyDetails to PartyFooter and makes description reactive

* Stop 1password icon from appearing in name field

* Use snapshot for reactive Edit party modal

* Fix Edit modal placeholder colors

* Fix bug with RaidCombobox and Farming

Selecting farming then opening the raid combobox *twice* consecutively would put you in no segment, so no raids appeared

Fixes #323

* Fix values staying in Edit team even if not saved

The values a user entered in the Edit team modal would persist even if the user hit cancel to close the modal. They wouldn't save to the server, but very confusing nonetheless. Now fixed.

* Fix unreadable colors in ElementToggle

* Fix button alignment in weapon modal

* Add text to filters button on small screens

The FilterBar showed a left aligned filter icon on mobile for months and it was driving me insane

* Remove extraneous code from Header

Including the party name, since it's at the top now

* Fix Alert at small sizes

* Make copy link toast work again

* Remove stylesheet links

* Fix remix toasts and alerts from both locations

The remix toast and alert was barely hooked up and not showing up when invoked from PartyHeader.

It now shows up whether you remix your own team (from PartyDropdown) or if you remix another person's team (from PartyHeader).
This commit is contained in:
Justin Edmund 2023-06-21 03:39:25 -07:00 committed by GitHub
parent 73b98db85e
commit 103ef7e1a2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 384 additions and 369 deletions

1
.gitignore vendored
View file

@ -58,6 +58,7 @@ public/images/mastery*
public/images/updates*
public/images/guidebooks*
public/images/raids*
public/images/gacha*
# Typescript v1 declaration files
typings/

View file

@ -47,32 +47,32 @@
&.fire {
background: var(--fire-bg);
color: var(--fire-text);
color: var(--fire-hover-text);
}
&.water {
background: var(--water-bg);
color: var(--water-text);
color: var(--water-hover-text);
}
&.earth {
background: var(--earth-bg);
color: var(--earth-text);
color: var(--earth-hover-text);
}
&.wind {
background: var(--wind-bg);
color: var(--wind-text);
color: var(--wind-hover-text);
}
&.dark {
background: var(--dark-bg);
color: var(--dark-text);
color: var(--dark-hover-text);
}
&.light {
background: var(--light-bg);
color: var(--light-text);
color: var(--light-hover-text);
}
}
}

View file

@ -102,6 +102,23 @@
}
}
.Filter.Button {
justify-content: center;
.Text {
display: none;
width: auto;
@include breakpoint(tablet) {
display: block;
}
@include breakpoint(phone) {
display: block;
}
}
}
.UserInfo {
align-items: center;
display: flex;

View file

@ -181,6 +181,7 @@ const FilterBar = (props: Props) => {
className={filterButtonClasses}
blended={true}
leftAccessoryIcon={<FilterIcon />}
text={t('filters.name')}
onClick={() => setFilterModalOpen(true)}
/>
</div>

View file

@ -7,7 +7,6 @@ import classNames from 'classnames'
import clonedeep from 'lodash.clonedeep'
import Link from 'next/link'
import api from '~utils/api'
import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import { getLocalId } from '~utils/localId'
@ -32,11 +31,8 @@ import Tooltip from '~components/common/Tooltip'
import * as Switch from '@radix-ui/react-switch'
import ChevronIcon from '~public/icons/Chevron.svg'
import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg'
import RemixIcon from '~public/icons/Remix.svg'
import PlusIcon from '~public/icons/Add.svg'
import SaveIcon from '~public/icons/Save.svg'
import './index.scss'
@ -51,7 +47,6 @@ const Header = () => {
const localeData = retrieveLocaleCookies()
// State management
const [copyToastOpen, setCopyToastOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false)
const [loginModalOpen, setLoginModalOpen] = useState(false)
const [signupModalOpen, setSignupModalOpen] = useState(false)
@ -64,7 +59,6 @@ const Header = () => {
const [originalName, setOriginalName] = useState('')
// Snapshots
const { account } = useSnapshot(accountState)
const { party: partySnapshot } = useSnapshot(appState)
// Subscribe to app state to listen for party name and
@ -108,15 +102,6 @@ const Header = () => {
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)
@ -142,23 +127,6 @@ const Header = () => {
router.push(router.asPath, undefined, { locale: language })
}
function copyToClipboard() {
const path = router.asPath.split('/')[1]
if (path === 'p') {
const el = document.createElement('input')
el.value = window.location.href
el.id = 'url-input'
document.body.appendChild(el)
el.select()
document.execCommand('copy')
el.remove()
setCopyToastOpen(true)
}
}
function logout() {
// Close menu
closeRightMenu()
@ -188,84 +156,6 @@ const Header = () => {
router.push('/new')
}
function remixTeam() {
setOriginalName(partySnapshot.name ? partySnapshot.name : t('no_title'))
if (partySnapshot.shortcode) {
const body = getLocalId()
api
.remix({ shortcode: partySnapshot.shortcode, body: body })
.then((response) => {
const remix = response.data.party
// Store the edit key in local storage
if (remix.edit_key) {
storeEditKey(remix.id, remix.edit_key)
setEditKey(remix.id, remix.user)
}
router.push(`/p/${remix.shortcode}`)
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 = () => {
let title = ''
let hasAccessory = false
const path = router.asPath.split('/')[1]
if (path === 'p') {
hasAccessory = true
if (appState.party && appState.party.name) {
title = appState.party.name
} else {
title = t('no_title')
}
} else {
title = ''
}
return title !== '' ? (
<Tooltip content={t('tooltips.copy_url')}>
<Button
blended={true}
rightAccessoryIcon={
path === 'p' && hasAccessory ? (
<LinkIcon className="stroke" />
) : undefined
}
text={title}
onClick={copyToClipboard}
/>
</Tooltip>
) : (
''
)
}
const profileImage = () => {
let image
@ -310,21 +200,6 @@ const Header = () => {
)
}
// Rendering: Toasts
const urlCopyToast = () => {
return (
<Toast
altText={t('toasts.copied')}
open={copyToastOpen}
duration={2400}
type="foreground"
content={t('toasts.copied')}
onOpenChange={handleCopyToastOpenChanged}
onCloseClick={handleCopyToastCloseClicked}
/>
)
}
const remixToast = () => {
return (
<Toast
@ -394,7 +269,6 @@ const Header = () => {
</DropdownMenuContent>
</DropdownMenu>
</div>
{!appState.errorCode ? pageTitle() : ''}
</section>
)
}
@ -564,8 +438,6 @@ const Header = () => {
<nav id="Header">
{left()}
{right()}
{urlCopyToast()}
{remixToast()}
{settingsModal()}
{loginModal()}
{signupModal()}

View file

@ -22,9 +22,14 @@
max-width: 30vw;
padding: $unit * 4;
@include breakpoint(tablet) {
max-width: inherit;
max-width: 60vw;
}
@include breakpoint(phone) {
max-width: inherit;
width: 60vw;
width: 70vw;
}
.description {
@ -41,5 +46,15 @@
display: flex;
align-self: flex-end;
gap: $unit;
@include breakpoint(phone) {
flex-direction: column-reverse;
align-self: center;
width: 100%;
.Button {
width: 100%;
}
}
}
}

View file

@ -0,0 +1,3 @@
.Joined .Input::placeholder {
color: var(--text-tertiary);
}

View file

@ -56,6 +56,7 @@ const CharLimitedFieldset: ForwardRefRenderFunction<HTMLInputElement, Props> = (
<div className={classNames({ Joined: true }, props.className)}>
<input
{...props}
data-1p-ignore
autoComplete="off"
className="Input"
type={props.type}

View file

@ -50,6 +50,6 @@
.Input::placeholder {
/* Chrome, Firefox, Opera, Safari 10.1+ */
color: var(--text-secondary) !important;
color: var(--text-secondary);
opacity: 1; /* Firefox */
}

View file

@ -8,7 +8,6 @@
padding: $unit;
transform-origin: var(--radix-popover-content-transform-origin);
width: var(--radix-popover-trigger-width);
min-width: 440px;
z-index: 5;
@include breakpoint(phone) {

View file

@ -19,7 +19,8 @@
color: var(--full-auto-text);
}
&.AutoGuard.On {
&.AutoGuard.On,
&.AutoSummon.On {
background: var(--auto-guard-bg);
color: var(--auto-guard-text);
}

View file

@ -46,7 +46,7 @@ const RemixTeamAlert = ({
<Trans i18nKey="modals.remix_team.description.viewer">
Remixing a team makes a copy of it in your account so you can make
your own changes.\n\nWould you like to remix{' '}
<strong>{{ name: 'HEY' }}</strong>?
<strong>{{ name: name }}</strong>?
</Trans>
)
}

View file

@ -1,5 +1,6 @@
import React, { useEffect, useRef, useState } from 'react'
import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'react-i18next'
import {
@ -22,6 +23,8 @@ import TableField from '~components/common/TableField'
import type { DetailsObject } from 'types'
import type { DialogProps } from '@radix-ui/react-dialog'
import { appState } from '~utils/appState'
import CheckIcon from '~public/icons/Check.svg'
import CrossIcon from '~public/icons/Cross.svg'
import './index.scss'
@ -31,14 +34,16 @@ interface Props extends DialogProps {
updateCallback: (details: DetailsObject) => void
}
const EditPartyModal = ({ party, updateCallback, ...props }: Props) => {
const EditPartyModal = ({ updateCallback, ...props }: Props) => {
// Set up router
const router = useRouter()
const locale = router.locale
// Set up translation
const { t } = useTranslation('common')
// Set up reactive state
const { party } = useSnapshot(appState)
// Refs
const headerRef = React.createRef<HTMLDivElement>()
const footerRef = React.createRef<HTMLDivElement>()
@ -54,6 +59,7 @@ const EditPartyModal = ({ party, updateCallback, ...props }: Props) => {
// States: Data
const [name, setName] = useState('')
const [description, setDescription] = useState('')
const [raid, setRaid] = useState<Raid>()
const [extra, setExtra] = useState(false)
const [chargeAttack, setChargeAttack] = useState(true)
@ -68,24 +74,15 @@ const EditPartyModal = ({ party, updateCallback, ...props }: Props) => {
// Hooks
useEffect(() => {
if (!party) return
setName(party.name)
setRaid(party.raid)
setAutoGuard(party.auto_guard)
setAutoSummon(party.auto_summon)
setFullAuto(party.full_auto)
setChargeAttack(party.charge_attack)
setClearTime(party.clear_time)
if (party.turn_count) setTurnCount(party.turn_count)
if (party.button_count) setButtonCount(party.button_count)
if (party.chain_count) setChainCount(party.chain_count)
persistFromState()
}, [party])
// Methods: Event handlers (Dialog)
function openChange() {
if (open) {
setOpen(false)
setCurrentSegment(0)
persistFromState()
if (props.onOpenChange) props.onOpenChange(false)
} else {
setOpen(true)
@ -176,6 +173,21 @@ const EditPartyModal = ({ party, updateCallback, ...props }: Props) => {
}
// Methods: Data methods
function persistFromState() {
if (!party) return
setName(party.name ? party.name : '')
setDescription(party.description ? party.description : '')
setRaid(party.raid)
setAutoGuard(party.autoGuard)
setAutoSummon(party.autoSummon)
setFullAuto(party.fullAuto)
setChargeAttack(party.chargeAttack)
setClearTime(party.clearTime)
if (party.turnCount) setTurnCount(party.turnCount)
if (party.buttonCount) setButtonCount(party.buttonCount)
if (party.chainCount) setChainCount(party.chainCount)
}
function updateDetails(event: React.MouseEvent) {
const descriptionValue = descriptionInput.current?.value
const details: DetailsObject = {
@ -272,9 +284,8 @@ const EditPartyModal = ({ party, updateCallback, ...props }: Props) => {
}
onChange={handleTextAreaChanged}
ref={descriptionInput}
>
{party ? party.description : ''}
</textarea>
defaultValue={description}
/>
</div>
)
}

View file

@ -7,7 +7,7 @@ import clonedeep from 'lodash.clonedeep'
import Alert from '~components/common/Alert'
import PartySegmentedControl from '~components/party/PartySegmentedControl'
import PartyDetails from '~components/party/PartyDetails'
import PartyFooter from '~components/party/PartyFooter'
import PartyHeader from '~components/party/PartyHeader'
import WeaponGrid from '~components/weapon/WeaponGrid'
import SummonGrid from '~components/summon/SummonGrid'
@ -145,37 +145,27 @@ const Party = (props: Props) => {
function formatDetailsObject(details: DetailsObject) {
const payload: { [key: string]: any } = {}
const mappings: { [key: string]: string } = {
name: 'name',
description: 'description',
chargeAttack: 'charge_attack',
fullAuto: 'full_auto',
autoGuard: 'auto_guard',
autoSummon: 'auto_summon',
clearTime: 'clear_time',
buttonCount: 'button_count',
chainCount: 'chain_count',
turnCount: 'turn_count',
extra: 'extra',
job: 'job_id',
guidebook1_id: 'guidebook1_id',
guidebook2_id: 'guidebook2_id',
guidebook3_id: 'guidebook3_id',
}
Object.entries(mappings).forEach(([key, value]) => {
if (details[key]) {
payload[value] = details[key]
}
})
if (details.name) payload.name = details.name
if (details.description) payload.description = details.description
if (details.raid) payload.raid_id = details.raid.id
if (details.chargeAttack != undefined)
payload.charge_attack = details.chargeAttack
if (details.fullAuto != undefined) payload.full_auto = details.fullAuto
if (details.autoGuard != undefined) payload.auto_guard = details.autoGuard
if (details.autoSummon != undefined)
payload.auto_summon = details.autoSummon
if (details.clearTime) payload.clear_time = details.clearTime
if (details.buttonCount) payload.button_count = details.buttonCount
if (details.chainCount) payload.chain_count = details.chainCount
if (details.turnCount) payload.turn_count = details.turnCount
if (details.extra != undefined) payload.extra = details.extra
if (details.job) payload.job_id = details.job.id
if (details.guidebook1_id) payload.guidebook1_id = details.guidebook1_id
if (details.guidebook2_id) payload.guidebook2_id = details.guidebook2_id
if (details.guidebook3_id) payload.guidebook3_id = details.guidebook3_id
if (Object.keys(payload).length >= 1) {
return { party: payload }
} else {
return {}
}
if (Object.keys(payload).length >= 1) return { party: payload }
else return {}
}
function cancelAlert() {
@ -275,6 +265,15 @@ const Party = (props: Props) => {
appState.party.jobSkills = team.job_skills
appState.party.accessory = team.accessory
appState.party.chargeAttack = team.charge_attack
appState.party.fullAuto = team.full_auto
appState.party.autoGuard = team.auto_guard
appState.party.autoSummon = team.auto_summon
appState.party.clearTime = team.clear_time
appState.party.buttonCount = team.button_count
appState.party.chainCount = team.chain_count
appState.party.turnCount = team.turn_count
appState.party.id = team.id
appState.party.shortcode = team.shortcode
appState.party.extra = team.extra
@ -455,7 +454,7 @@ const Party = (props: Props) => {
<section id="Party">{currentGrid()}</section>
<PartyDetails
<PartyFooter
party={props.team}
new={props.new || false}
editable={party.editable}

View file

@ -2,8 +2,7 @@
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { subscribe, useSnapshot } from 'valtio'
import { Trans, useTranslation } from 'next-i18next'
import Link from 'next/link'
import { useTranslation } from 'next-i18next'
import classNames from 'classnames'
// Dependencies: Common
@ -125,22 +124,27 @@ const PartyDropdown = ({
// Toasts / Copy URL
function handleCopyToastOpenChanged(open: boolean) {
setCopyToastOpen(open)
setCopyToastOpen(!open)
}
function handleCopyToastCloseClicked() {
setCopyToastOpen(false)
}
// Toasts / Remix team
// Toasts: Remix team
function handleRemixToastOpenChanged(open: boolean) {
setRemixToastOpen(open)
setRemixToastOpen(!open)
}
function handleRemixToastCloseClicked() {
setRemixToastOpen(false)
}
function remixCallback() {
setRemixToastOpen(true)
remixTeamCallback()
}
const editableItems = () => {
return (
<>
@ -185,10 +189,23 @@ const PartyDropdown = ({
<RemixTeamAlert
creator={editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')}
name={partySnapshot.name || t('no_title')}
open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange}
remixCallback={remixTeamCallback}
remixCallback={remixCallback}
/>
<RemixedToast
open={remixToastOpen}
partyName={partySnapshot.name || t('no_title')}
onOpenChange={handleRemixToastOpenChanged}
onCloseClick={handleRemixToastCloseClicked}
/>
<UrlCopiedToast
open={copyToastOpen}
onOpenChange={handleCopyToastOpenChanged}
onCloseClick={handleCopyToastCloseClicked}
/>
</>
)

View file

@ -1,4 +1,4 @@
.DetailsWrapper {
.FooterWrapper {
display: flex;
flex-direction: column;
gap: $unit-2x;
@ -16,10 +16,13 @@
}
}
.PartyDetails {
.PartyFooter {
box-sizing: border-box;
display: none;
line-height: 1.4;
white-space: pre-wrap;
margin: 0 auto $unit-2x;
margin-bottom: $unit-12x;
min-height: 10vh;
max-width: $unit * 94;
overflow: hidden;
width: 100%;
@ -27,11 +30,6 @@
@include breakpoint(phone) {
padding: 0 $unit;
}
&.Visible {
// margin-bottom: $unit-12x;
}
&.Editable {
gap: $unit;
@ -174,11 +172,6 @@
}
}
&.ReadOnly {
box-sizing: border-box;
line-height: 1.4;
white-space: pre-wrap;
&.Visible {
display: block;
}
@ -285,7 +278,6 @@
}
}
}
}
.PartyInfo {
box-sizing: border-box;

View file

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import clonedeep from 'lodash.clonedeep'
@ -27,10 +28,12 @@ interface Props {
updateCallback: (details: DetailsObject) => void
}
const PartyDetails = (props: Props) => {
const PartyFooter = (props: Props) => {
const { t } = useTranslation('common')
const router = useRouter()
const { party } = useSnapshot(appState)
const youtubeUrlRegex =
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
@ -40,16 +43,10 @@ const PartyDetails = (props: Props) => {
const [embeddedDescription, setEmbeddedDescription] =
useState<React.ReactNode>()
const readOnlyClasses = classNames({
PartyDetails: true,
ReadOnly: true,
Visible: !open,
})
useEffect(() => {
// Extract the video IDs from the description
if (appState.party.description) {
const videoIds = extractYoutubeVideoIds(appState.party.description)
if (party.description) {
const videoIds = extractYoutubeVideoIds(party.description)
// Fetch the video titles for each ID
const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id))
@ -58,7 +55,7 @@ const PartyDetails = (props: Props) => {
Promise.all(fetchPromises).then((videoTitles) => {
// Replace the video URLs in the description with LiteYoutubeEmbed elements
const newDescription = reactStringReplace(
appState.party.description,
party.description,
youtubeUrlRegex,
(match, i) => (
<LiteYouTubeEmbed
@ -77,7 +74,7 @@ const PartyDetails = (props: Props) => {
} else {
setEmbeddedDescription('')
}
}, [appState.party.description])
}, [party.description])
async function fetchYoutubeData(videoId: string) {
return await youtube
@ -173,14 +170,6 @@ const PartyDetails = (props: Props) => {
})
}
const readOnly = () => {
return (
<section className={readOnlyClasses}>
<Linkify>{embeddedDescription}</Linkify>
</section>
)
}
const remixSection = () => {
return (
<section className="Remixes">
@ -192,10 +181,14 @@ const PartyDetails = (props: Props) => {
return (
<>
<section className="DetailsWrapper">{readOnly()}</section>
<section className="FooterWrapper">
<section className="PartyFooter">
<Linkify>{embeddedDescription}</Linkify>
</section>
</section>
{remixes && remixes.length > 0 ? remixSection() : ''}
</>
)
}
export default PartyDetails
export default PartyFooter

View file

@ -12,6 +12,7 @@ import Token from '~components/common/Token'
import EditPartyModal from '~components/party/EditPartyModal'
import PartyDropdown from '~components/party/PartyDropdown'
import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import { formatTimeAgo } from '~utils/timeAgo'
@ -23,7 +24,9 @@ import SaveIcon from '~public/icons/Save.svg'
import type { DetailsObject } from 'types'
import './index.scss'
import api from '~utils/api'
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
import RemixedToast from '~components/toasts/RemixedToast'
import { set } from 'local-storage'
// Props
interface Props {
@ -44,12 +47,16 @@ const PartyHeader = (props: Props) => {
const { party: partySnapshot } = useSnapshot(appState)
const [name, setName] = useState('')
// State: Component
const [remixAlertOpen, setRemixAlertOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false)
// State: Data
const [name, setName] = useState('')
const [chargeAttack, setChargeAttack] = useState(true)
const [fullAuto, setFullAuto] = useState(false)
const [autoGuard, setAutoGuard] = useState(false)
const [autoSummon, setAutoSummon] = useState(false)
const [buttonCount, setButtonCount] = useState<number | undefined>(undefined)
const [chainCount, setChainCount] = useState<number | undefined>(undefined)
const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
@ -78,6 +85,7 @@ const PartyHeader = (props: Props) => {
setName(props.party.name)
setAutoGuard(props.party.auto_guard)
setFullAuto(props.party.full_auto)
setAutoSummon(props.party.auto_summon)
setChargeAttack(props.party.charge_attack)
setClearTime(props.party.clear_time)
if (props.party.turn_count) setTurnCount(props.party.turn_count)
@ -155,6 +163,32 @@ const PartyHeader = (props: Props) => {
)
}
// Actions: Remix team
function remixTeamCallback() {
setRemixToastOpen(true)
props.remixCallback()
}
// Alerts: Remix team
function openRemixTeamAlert() {
setRemixAlertOpen(true)
}
function handleRemixTeamAlertChange(open: boolean) {
setRemixAlertOpen(open)
}
// Toasts: Remix team
function handleRemixToastOpenChanged(open: boolean) {
setRemixToastOpen(!open)
}
function handleRemixToastCloseClicked() {
setRemixToastOpen(false)
}
// Rendering
const userBlock = (username?: string, picture?: string, element?: string) => {
return (
<div className={userClass}>
@ -212,12 +246,12 @@ const PartyHeader = (props: Props) => {
<Token
className={classNames({
ChargeAttack: true,
On: chargeAttack,
Off: !chargeAttack,
On: party.chargeAttack,
Off: !party.chargeAttack,
})}
>
{`${t('party.details.labels.charge_attack')} ${
chargeAttack ? 'On' : 'Off'
party.chargeAttack ? 'On' : 'Off'
}`}
</Token>
)
@ -226,11 +260,13 @@ const PartyHeader = (props: Props) => {
<Token
className={classNames({
FullAuto: true,
On: fullAuto,
Off: !fullAuto,
On: party.fullAuto,
Off: !party.fullAuto,
})}
>
{`${t('party.details.labels.full_auto')} ${fullAuto ? 'On' : 'Off'}`}
{`${t('party.details.labels.full_auto')} ${
party.fullAuto ? 'On' : 'Off'
}`}
</Token>
)
@ -238,37 +274,57 @@ const PartyHeader = (props: Props) => {
<Token
className={classNames({
AutoGuard: true,
On: autoGuard,
Off: !autoGuard,
On: party.autoGuard,
Off: !party.autoGuard,
})}
>
{`${t('party.details.labels.auto_guard')} ${autoGuard ? 'On' : 'Off'}`}
{`${t('party.details.labels.auto_guard')} ${
party.autoGuard ? 'On' : 'Off'
}`}
</Token>
)
const autoSummonToken = (
<Token
className={classNames({
AutoSummon: true,
On: party.autoSummon,
Off: !party.autoSummon,
})}
>
{`${t('party.details.labels.auto_summon')} ${
party.autoSummon ? 'On' : 'Off'
}`}
</Token>
)
const turnCountToken = (
<Token>
{t('party.details.turns.with_count', {
count: turnCount,
count: party.turnCount,
})}
</Token>
)
const buttonChainToken = () => {
if (buttonCount || chainCount) {
if (party.buttonCount || party.chainCount) {
let string = ''
if (buttonCount && buttonCount > 0) {
string += `${buttonCount}b`
if (party.buttonCount && party.buttonCount > 0) {
string += `${party.buttonCount}b`
}
if (!buttonCount && chainCount && chainCount > 0) {
string += `0${t('party.details.suffix.buttons')}${chainCount}${t(
if (!party.buttonCount && party.chainCount && party.chainCount > 0) {
string += `0${t('party.details.suffix.buttons')}${party.chainCount}${t(
'party.details.suffix.chains'
)}`
} else if (buttonCount && chainCount && chainCount > 0) {
string += `${chainCount}${t('party.details.suffix.chains')}`
} else if (buttonCount && !chainCount) {
} else if (
party.buttonCount &&
party.chainCount &&
party.chainCount > 0
) {
string += `${party.chainCount}${t('party.details.suffix.chains')}`
} else if (party.buttonCount && !party.chainCount) {
string += `0${t('party.details.suffix.chains')}`
}
@ -277,8 +333,8 @@ const PartyHeader = (props: Props) => {
}
const clearTimeToken = () => {
const minutes = Math.floor(clearTime / 60)
const seconds = clearTime - minutes * 60
const minutes = Math.floor(party.clearTime / 60)
const seconds = party.clearTime - minutes * 60
let string = ''
if (minutes > 0)
@ -296,8 +352,9 @@ const PartyHeader = (props: Props) => {
{chargeAttackToken}
{fullAutoToken}
{autoGuardToken}
{turnCount ? turnCountToken : ''}
{clearTime > 0 ? clearTimeToken() : ''}
{autoSummonToken}
{party.turnCount ? turnCountToken : ''}
{party.clearTime > 0 ? clearTimeToken() : ''}
{buttonChainToken()}
</section>
)
@ -329,7 +386,7 @@ const PartyHeader = (props: Props) => {
leftAccessoryIcon={<RemixIcon />}
className="Remix"
text={t('buttons.remix')}
onClick={props.remixCallback}
onClick={openRemixTeamAlert}
/>
</Tooltip>
)
@ -341,8 +398,8 @@ const PartyHeader = (props: Props) => {
<div className="PartyInfo">
<div className="Left">
<div className="Header">
<h1 className={name ? '' : 'empty'}>
{name ? name : t('no_title')}
<h1 className={party.name ? '' : 'empty'}>
{party.name ? party.name : t('no_title')}
</h1>
{party.remix && party.sourceParty ? (
<Tooltip content={t('tooltips.source')}>
@ -398,6 +455,21 @@ const PartyHeader = (props: Props) => {
</div>
<section className={classes}>{renderTokens()}</section>
</section>
<RemixTeamAlert
creator={props.editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')}
open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange}
remixCallback={remixTeamCallback}
/>
<RemixedToast
open={remixToastOpen}
partyName={props.party?.name || t('no_title')}
onOpenChange={handleRemixToastOpenChanged}
onCloseClick={handleRemixToastCloseClicked}
/>
</>
)
}

View file

@ -1,5 +1,6 @@
.Combobox.Raid {
box-sizing: border-box;
min-width: 440px;
.Header {
background: var(--dialog-bg);
@ -184,6 +185,10 @@
}
}
.SelectTrigger.Raid .Value.Empty {
color: var(--text-tertiary);
}
.Filters .SelectTrigger.Raid {
& > span {
overflow: hidden;

View file

@ -105,19 +105,25 @@ const RaidCombobox = (props: Props) => {
// Set current raid and section when the component mounts
useEffect(() => {
if (appState.party.raid) {
setCurrentRaid(appState.party.raid)
setCurrentSection(appState.party.raid.group.section)
} else if (props.showAllRaidsOption && !currentRaid) {
setCurrentRaid(allRaidsOption)
}
// if (appState.party.raid) {
// setCurrentRaid(appState.party.raid)
// if (appState.party.raid.group.section > 0) {
// setCurrentSection(appState.party.raid.group.section)
// } else {
// setCurrentSection(1)
// }
// } else if (props.showAllRaidsOption && !currentRaid) {
// setCurrentRaid(allRaidsOption)
// }
}, [])
// Set current raid and section when the current raid changes
useEffect(() => {
if (props.currentRaid) {
setCurrentRaid(props.currentRaid)
if (appState.party.raid && appState.party.raid.group.section > 0)
setCurrentSection(props.currentRaid.group.section)
else setCurrentSection(1)
}
}, [props.currentRaid])
@ -260,7 +266,11 @@ const RaidCombobox = (props: Props) => {
// Toggle the open state of the combobox
function toggleOpen() {
if (open) {
if (currentRaid && currentRaid.slug !== 'all') {
if (
currentRaid &&
currentRaid.slug !== 'all' &&
currentRaid.group.section > 0
) {
setCurrentSection(currentRaid.group.section)
}
setScrolled(false)

View file

@ -1,9 +1,7 @@
import React from 'react'
import React, { useEffect } from 'react'
import Toast from '~components/common/Toast'
import { Trans, useTranslation } from 'next-i18next'
import './index.scss'
interface Props {
partyName: string
open: boolean
@ -19,7 +17,9 @@ const RemixedToast = ({
onCloseClick,
}: Props) => {
const { t } = useTranslation('common')
useEffect(() => {
console.log(partyName)
}, [])
// Methods: Event handlers
function handleOpenChange() {
onOpenChange(open)

View file

@ -1,7 +1,5 @@
import React from 'react'
import Toast from '~components/common/Toast'
import './index.scss'
import { useTranslation } from 'next-i18next'
interface Props {

View file

@ -394,6 +394,7 @@ const WeaponModal = ({
{gridWeapon.object.awakenings ? awakeningSelect() : ''}
</div>
<div className="DialogFooter" ref={footerRef}>
<div className="actions">
<Button
contained={true}
onClick={updateWeapon}
@ -401,6 +402,7 @@ const WeaponModal = ({
text={t('modals.weapon.buttons.confirm')}
/>
</div>
</div>
</DialogContent>
</Dialog>
)

View file

@ -93,6 +93,7 @@
"unauthorized": "You don't have permission to perform that action"
},
"filters": {
"name": "Filter",
"labels": {
"element": "Element",
"series": "Series",
@ -383,6 +384,7 @@
"charge_attack": "Charge Attack",
"full_auto": "Full Auto",
"auto_guard": "Auto Guard",
"auto_summon": "Auto Summon",
"turn_count": "Turn count",
"button_chain": "Buttons/Chains",
"clear_time": "Clear time"

View file

@ -93,6 +93,7 @@
"unauthorized": "行ったアクションを実行する権限がありません"
},
"filters": {
"name": "フィルター",
"labels": {
"element": "属性",
"series": "シリーズ",
@ -380,6 +381,7 @@
"charge_attack": "奥義",
"full_auto": "フルオート",
"auto_guard": "オートガード",
"auto_summon": "オート召喚",
"turn_count": "経過ターン",
"button_chain": "ポチチェイン",
"clear_time": "討伐時間"

View file

@ -49,6 +49,7 @@ interface AppState {
element: number
fullAuto: boolean
autoGuard: boolean
autoSummon: boolean
chargeAttack: boolean
clearTime: number
buttonCount?: number
@ -110,6 +111,7 @@ export const initialAppState: AppState = {
raid: undefined,
fullAuto: false,
autoGuard: false,
autoSummon: false,
chargeAttack: true,
clearTime: 0,
buttonCount: undefined,