Merge branch 'staging' of github.com:jedmund/hensei-web into staging

This commit is contained in:
Justin Edmund 2023-06-21 03:44:23 -07:00
commit 9de87abd1e
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/updates*
public/images/guidebooks* public/images/guidebooks*
public/images/raids* public/images/raids*
public/images/gacha*
# Typescript v1 declaration files # Typescript v1 declaration files
typings/ typings/

View file

@ -47,32 +47,32 @@
&.fire { &.fire {
background: var(--fire-bg); background: var(--fire-bg);
color: var(--fire-text); color: var(--fire-hover-text);
} }
&.water { &.water {
background: var(--water-bg); background: var(--water-bg);
color: var(--water-text); color: var(--water-hover-text);
} }
&.earth { &.earth {
background: var(--earth-bg); background: var(--earth-bg);
color: var(--earth-text); color: var(--earth-hover-text);
} }
&.wind { &.wind {
background: var(--wind-bg); background: var(--wind-bg);
color: var(--wind-text); color: var(--wind-hover-text);
} }
&.dark { &.dark {
background: var(--dark-bg); background: var(--dark-bg);
color: var(--dark-text); color: var(--dark-hover-text);
} }
&.light { &.light {
background: var(--light-bg); 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 { .UserInfo {
align-items: center; align-items: center;
display: flex; display: flex;

View file

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

View file

@ -7,7 +7,6 @@ import classNames from 'classnames'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
import Link from 'next/link' import Link from 'next/link'
import api from '~utils/api'
import { accountState, initialAccountState } from '~utils/accountState' import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState' import { appState, initialAppState } from '~utils/appState'
import { getLocalId } from '~utils/localId' import { getLocalId } from '~utils/localId'
@ -32,11 +31,8 @@ import Tooltip from '~components/common/Tooltip'
import * as Switch from '@radix-ui/react-switch' import * as Switch from '@radix-ui/react-switch'
import ChevronIcon from '~public/icons/Chevron.svg' import ChevronIcon from '~public/icons/Chevron.svg'
import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg' import MenuIcon from '~public/icons/Menu.svg'
import RemixIcon from '~public/icons/Remix.svg'
import PlusIcon from '~public/icons/Add.svg' import PlusIcon from '~public/icons/Add.svg'
import SaveIcon from '~public/icons/Save.svg'
import './index.scss' import './index.scss'
@ -51,7 +47,6 @@ const Header = () => {
const localeData = retrieveLocaleCookies() const localeData = retrieveLocaleCookies()
// State management // State management
const [copyToastOpen, setCopyToastOpen] = useState(false)
const [remixToastOpen, setRemixToastOpen] = useState(false) const [remixToastOpen, setRemixToastOpen] = useState(false)
const [loginModalOpen, setLoginModalOpen] = useState(false) const [loginModalOpen, setLoginModalOpen] = useState(false)
const [signupModalOpen, setSignupModalOpen] = useState(false) const [signupModalOpen, setSignupModalOpen] = useState(false)
@ -64,7 +59,6 @@ const Header = () => {
const [originalName, setOriginalName] = useState('') const [originalName, setOriginalName] = useState('')
// Snapshots // Snapshots
const { account } = useSnapshot(accountState)
const { party: partySnapshot } = useSnapshot(appState) const { party: partySnapshot } = useSnapshot(appState)
// Subscribe to app state to listen for party name and // Subscribe to app state to listen for party name and
@ -108,15 +102,6 @@ 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) // Methods: Event handlers (Remix toasts)
function handleRemixToastOpenChanged(open: boolean) { function handleRemixToastOpenChanged(open: boolean) {
setRemixToastOpen(open) setRemixToastOpen(open)
@ -142,23 +127,6 @@ const Header = () => {
router.push(router.asPath, undefined, { locale: language }) 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() { function logout() {
// Close menu // Close menu
closeRightMenu() closeRightMenu()
@ -188,84 +156,6 @@ const Header = () => {
router.push('/new') 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 = () => { const profileImage = () => {
let image 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 = () => { const remixToast = () => {
return ( return (
<Toast <Toast
@ -394,7 +269,6 @@ const Header = () => {
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
{!appState.errorCode ? pageTitle() : ''}
</section> </section>
) )
} }
@ -564,8 +438,6 @@ const Header = () => {
<nav id="Header"> <nav id="Header">
{left()} {left()}
{right()} {right()}
{urlCopyToast()}
{remixToast()}
{settingsModal()} {settingsModal()}
{loginModal()} {loginModal()}
{signupModal()} {signupModal()}

View file

@ -22,9 +22,14 @@
max-width: 30vw; max-width: 30vw;
padding: $unit * 4; padding: $unit * 4;
@include breakpoint(tablet) {
max-width: inherit;
max-width: 60vw;
}
@include breakpoint(phone) { @include breakpoint(phone) {
max-width: inherit; max-width: inherit;
width: 60vw; width: 70vw;
} }
.description { .description {
@ -41,5 +46,15 @@
display: flex; display: flex;
align-self: flex-end; align-self: flex-end;
gap: $unit; 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)}> <div className={classNames({ Joined: true }, props.className)}>
<input <input
{...props} {...props}
data-1p-ignore
autoComplete="off" autoComplete="off"
className="Input" className="Input"
type={props.type} type={props.type}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import clonedeep from 'lodash.clonedeep'
import Alert from '~components/common/Alert' import Alert from '~components/common/Alert'
import PartySegmentedControl from '~components/party/PartySegmentedControl' 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 PartyHeader from '~components/party/PartyHeader'
import WeaponGrid from '~components/weapon/WeaponGrid' import WeaponGrid from '~components/weapon/WeaponGrid'
import SummonGrid from '~components/summon/SummonGrid' import SummonGrid from '~components/summon/SummonGrid'
@ -145,37 +145,27 @@ const Party = (props: Props) => {
function formatDetailsObject(details: DetailsObject) { function formatDetailsObject(details: DetailsObject) {
const payload: { [key: string]: any } = {} const payload: { [key: string]: any } = {}
const mappings: { [key: string]: string } = { if (details.name) payload.name = details.name
name: 'name', if (details.description) payload.description = details.description
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.raid) payload.raid_id = details.raid.id 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) { if (Object.keys(payload).length >= 1) return { party: payload }
return { party: payload } else return {}
} else {
return {}
}
} }
function cancelAlert() { function cancelAlert() {
@ -275,6 +265,15 @@ const Party = (props: Props) => {
appState.party.jobSkills = team.job_skills appState.party.jobSkills = team.job_skills
appState.party.accessory = team.accessory 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.id = team.id
appState.party.shortcode = team.shortcode appState.party.shortcode = team.shortcode
appState.party.extra = team.extra appState.party.extra = team.extra
@ -455,7 +454,7 @@ const Party = (props: Props) => {
<section id="Party">{currentGrid()}</section> <section id="Party">{currentGrid()}</section>
<PartyDetails <PartyFooter
party={props.team} party={props.team}
new={props.new || false} new={props.new || false}
editable={party.editable} editable={party.editable}

View file

@ -2,8 +2,7 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { subscribe, useSnapshot } from 'valtio' import { subscribe, useSnapshot } from 'valtio'
import { Trans, useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import Link from 'next/link'
import classNames from 'classnames' import classNames from 'classnames'
// Dependencies: Common // Dependencies: Common
@ -125,22 +124,27 @@ const PartyDropdown = ({
// Toasts / Copy URL // Toasts / Copy URL
function handleCopyToastOpenChanged(open: boolean) { function handleCopyToastOpenChanged(open: boolean) {
setCopyToastOpen(open) setCopyToastOpen(!open)
} }
function handleCopyToastCloseClicked() { function handleCopyToastCloseClicked() {
setCopyToastOpen(false) setCopyToastOpen(false)
} }
// Toasts / Remix team // Toasts: Remix team
function handleRemixToastOpenChanged(open: boolean) { function handleRemixToastOpenChanged(open: boolean) {
setRemixToastOpen(open) setRemixToastOpen(!open)
} }
function handleRemixToastCloseClicked() { function handleRemixToastCloseClicked() {
setRemixToastOpen(false) setRemixToastOpen(false)
} }
function remixCallback() {
setRemixToastOpen(true)
remixTeamCallback()
}
const editableItems = () => { const editableItems = () => {
return ( return (
<> <>
@ -185,10 +189,23 @@ const PartyDropdown = ({
<RemixTeamAlert <RemixTeamAlert
creator={editable} creator={editable}
name={partySnapshot.name ? partySnapshot.name : t('no_title')} name={partySnapshot.name || t('no_title')}
open={remixAlertOpen} open={remixAlertOpen}
onOpenChange={handleRemixTeamAlertChange} 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; display: flex;
flex-direction: column; flex-direction: column;
gap: $unit-2x; gap: $unit-2x;
@ -16,10 +16,13 @@
} }
} }
.PartyDetails { .PartyFooter {
box-sizing: border-box; box-sizing: border-box;
display: none; line-height: 1.4;
white-space: pre-wrap;
margin: 0 auto $unit-2x; margin: 0 auto $unit-2x;
margin-bottom: $unit-12x;
min-height: 10vh;
max-width: $unit * 94; max-width: $unit * 94;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
@ -27,11 +30,6 @@
@include breakpoint(phone) { @include breakpoint(phone) {
padding: 0 $unit; padding: 0 $unit;
} }
&.Visible {
// margin-bottom: $unit-12x;
}
&.Editable { &.Editable {
gap: $unit; gap: $unit;
@ -174,11 +172,6 @@
} }
} }
&.ReadOnly {
box-sizing: border-box;
line-height: 1.4;
white-space: pre-wrap;
&.Visible { &.Visible {
display: block; display: block;
} }
@ -285,7 +278,6 @@
} }
} }
} }
}
.PartyInfo { .PartyInfo {
box-sizing: border-box; box-sizing: border-box;

View file

@ -1,5 +1,6 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next' import { useTranslation } from 'next-i18next'
import clonedeep from 'lodash.clonedeep' import clonedeep from 'lodash.clonedeep'
@ -27,10 +28,12 @@ interface Props {
updateCallback: (details: DetailsObject) => void updateCallback: (details: DetailsObject) => void
} }
const PartyDetails = (props: Props) => { const PartyFooter = (props: Props) => {
const { t } = useTranslation('common') const { t } = useTranslation('common')
const router = useRouter() const router = useRouter()
const { party } = useSnapshot(appState)
const youtubeUrlRegex = const youtubeUrlRegex =
/(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g /(?:https:\/\/www\.youtube\.com\/watch\?v=|https:\/\/youtu\.be\/)([\w-]+)/g
@ -40,16 +43,10 @@ const PartyDetails = (props: Props) => {
const [embeddedDescription, setEmbeddedDescription] = const [embeddedDescription, setEmbeddedDescription] =
useState<React.ReactNode>() useState<React.ReactNode>()
const readOnlyClasses = classNames({
PartyDetails: true,
ReadOnly: true,
Visible: !open,
})
useEffect(() => { useEffect(() => {
// Extract the video IDs from the description // Extract the video IDs from the description
if (appState.party.description) { if (party.description) {
const videoIds = extractYoutubeVideoIds(appState.party.description) const videoIds = extractYoutubeVideoIds(party.description)
// Fetch the video titles for each ID // Fetch the video titles for each ID
const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id)) const fetchPromises = videoIds.map(({ id }) => fetchYoutubeData(id))
@ -58,7 +55,7 @@ const PartyDetails = (props: Props) => {
Promise.all(fetchPromises).then((videoTitles) => { Promise.all(fetchPromises).then((videoTitles) => {
// Replace the video URLs in the description with LiteYoutubeEmbed elements // Replace the video URLs in the description with LiteYoutubeEmbed elements
const newDescription = reactStringReplace( const newDescription = reactStringReplace(
appState.party.description, party.description,
youtubeUrlRegex, youtubeUrlRegex,
(match, i) => ( (match, i) => (
<LiteYouTubeEmbed <LiteYouTubeEmbed
@ -77,7 +74,7 @@ const PartyDetails = (props: Props) => {
} else { } else {
setEmbeddedDescription('') setEmbeddedDescription('')
} }
}, [appState.party.description]) }, [party.description])
async function fetchYoutubeData(videoId: string) { async function fetchYoutubeData(videoId: string) {
return await youtube return await youtube
@ -173,14 +170,6 @@ const PartyDetails = (props: Props) => {
}) })
} }
const readOnly = () => {
return (
<section className={readOnlyClasses}>
<Linkify>{embeddedDescription}</Linkify>
</section>
)
}
const remixSection = () => { const remixSection = () => {
return ( return (
<section className="Remixes"> <section className="Remixes">
@ -192,10 +181,14 @@ const PartyDetails = (props: Props) => {
return ( return (
<> <>
<section className="DetailsWrapper">{readOnly()}</section> <section className="FooterWrapper">
<section className="PartyFooter">
<Linkify>{embeddedDescription}</Linkify>
</section>
</section>
{remixes && remixes.length > 0 ? remixSection() : ''} {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 EditPartyModal from '~components/party/EditPartyModal'
import PartyDropdown from '~components/party/PartyDropdown' import PartyDropdown from '~components/party/PartyDropdown'
import api from '~utils/api'
import { accountState } from '~utils/accountState' import { accountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState' import { appState, initialAppState } from '~utils/appState'
import { formatTimeAgo } from '~utils/timeAgo' import { formatTimeAgo } from '~utils/timeAgo'
@ -23,7 +24,9 @@ import SaveIcon from '~public/icons/Save.svg'
import type { DetailsObject } from 'types' import type { DetailsObject } from 'types'
import './index.scss' 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 // Props
interface Props { interface Props {
@ -44,12 +47,16 @@ const PartyHeader = (props: Props) => {
const { party: partySnapshot } = useSnapshot(appState) 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 [chargeAttack, setChargeAttack] = useState(true)
const [fullAuto, setFullAuto] = useState(false) const [fullAuto, setFullAuto] = useState(false)
const [autoGuard, setAutoGuard] = useState(false) const [autoGuard, setAutoGuard] = useState(false)
const [autoSummon, setAutoSummon] = useState(false)
const [buttonCount, setButtonCount] = useState<number | undefined>(undefined) const [buttonCount, setButtonCount] = useState<number | undefined>(undefined)
const [chainCount, setChainCount] = useState<number | undefined>(undefined) const [chainCount, setChainCount] = useState<number | undefined>(undefined)
const [turnCount, setTurnCount] = useState<number | undefined>(undefined) const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
@ -78,6 +85,7 @@ const PartyHeader = (props: Props) => {
setName(props.party.name) setName(props.party.name)
setAutoGuard(props.party.auto_guard) setAutoGuard(props.party.auto_guard)
setFullAuto(props.party.full_auto) setFullAuto(props.party.full_auto)
setAutoSummon(props.party.auto_summon)
setChargeAttack(props.party.charge_attack) setChargeAttack(props.party.charge_attack)
setClearTime(props.party.clear_time) setClearTime(props.party.clear_time)
if (props.party.turn_count) setTurnCount(props.party.turn_count) 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) => { const userBlock = (username?: string, picture?: string, element?: string) => {
return ( return (
<div className={userClass}> <div className={userClass}>
@ -212,12 +246,12 @@ const PartyHeader = (props: Props) => {
<Token <Token
className={classNames({ className={classNames({
ChargeAttack: true, ChargeAttack: true,
On: chargeAttack, On: party.chargeAttack,
Off: !chargeAttack, Off: !party.chargeAttack,
})} })}
> >
{`${t('party.details.labels.charge_attack')} ${ {`${t('party.details.labels.charge_attack')} ${
chargeAttack ? 'On' : 'Off' party.chargeAttack ? 'On' : 'Off'
}`} }`}
</Token> </Token>
) )
@ -226,11 +260,13 @@ const PartyHeader = (props: Props) => {
<Token <Token
className={classNames({ className={classNames({
FullAuto: true, FullAuto: true,
On: fullAuto, On: party.fullAuto,
Off: !fullAuto, Off: !party.fullAuto,
})} })}
> >
{`${t('party.details.labels.full_auto')} ${fullAuto ? 'On' : 'Off'}`} {`${t('party.details.labels.full_auto')} ${
party.fullAuto ? 'On' : 'Off'
}`}
</Token> </Token>
) )
@ -238,37 +274,57 @@ const PartyHeader = (props: Props) => {
<Token <Token
className={classNames({ className={classNames({
AutoGuard: true, AutoGuard: true,
On: autoGuard, On: party.autoGuard,
Off: !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> </Token>
) )
const turnCountToken = ( const turnCountToken = (
<Token> <Token>
{t('party.details.turns.with_count', { {t('party.details.turns.with_count', {
count: turnCount, count: party.turnCount,
})} })}
</Token> </Token>
) )
const buttonChainToken = () => { const buttonChainToken = () => {
if (buttonCount || chainCount) { if (party.buttonCount || party.chainCount) {
let string = '' let string = ''
if (buttonCount && buttonCount > 0) { if (party.buttonCount && party.buttonCount > 0) {
string += `${buttonCount}b` string += `${party.buttonCount}b`
} }
if (!buttonCount && chainCount && chainCount > 0) { if (!party.buttonCount && party.chainCount && party.chainCount > 0) {
string += `0${t('party.details.suffix.buttons')}${chainCount}${t( string += `0${t('party.details.suffix.buttons')}${party.chainCount}${t(
'party.details.suffix.chains' 'party.details.suffix.chains'
)}` )}`
} else if (buttonCount && chainCount && chainCount > 0) { } else if (
string += `${chainCount}${t('party.details.suffix.chains')}` party.buttonCount &&
} else if (buttonCount && !chainCount) { 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')}` string += `0${t('party.details.suffix.chains')}`
} }
@ -277,8 +333,8 @@ const PartyHeader = (props: Props) => {
} }
const clearTimeToken = () => { const clearTimeToken = () => {
const minutes = Math.floor(clearTime / 60) const minutes = Math.floor(party.clearTime / 60)
const seconds = clearTime - minutes * 60 const seconds = party.clearTime - minutes * 60
let string = '' let string = ''
if (minutes > 0) if (minutes > 0)
@ -296,8 +352,9 @@ const PartyHeader = (props: Props) => {
{chargeAttackToken} {chargeAttackToken}
{fullAutoToken} {fullAutoToken}
{autoGuardToken} {autoGuardToken}
{turnCount ? turnCountToken : ''} {autoSummonToken}
{clearTime > 0 ? clearTimeToken() : ''} {party.turnCount ? turnCountToken : ''}
{party.clearTime > 0 ? clearTimeToken() : ''}
{buttonChainToken()} {buttonChainToken()}
</section> </section>
) )
@ -329,7 +386,7 @@ const PartyHeader = (props: Props) => {
leftAccessoryIcon={<RemixIcon />} leftAccessoryIcon={<RemixIcon />}
className="Remix" className="Remix"
text={t('buttons.remix')} text={t('buttons.remix')}
onClick={props.remixCallback} onClick={openRemixTeamAlert}
/> />
</Tooltip> </Tooltip>
) )
@ -341,8 +398,8 @@ const PartyHeader = (props: Props) => {
<div className="PartyInfo"> <div className="PartyInfo">
<div className="Left"> <div className="Left">
<div className="Header"> <div className="Header">
<h1 className={name ? '' : 'empty'}> <h1 className={party.name ? '' : 'empty'}>
{name ? name : t('no_title')} {party.name ? party.name : t('no_title')}
</h1> </h1>
{party.remix && party.sourceParty ? ( {party.remix && party.sourceParty ? (
<Tooltip content={t('tooltips.source')}> <Tooltip content={t('tooltips.source')}>
@ -398,6 +455,21 @@ const PartyHeader = (props: Props) => {
</div> </div>
<section className={classes}>{renderTokens()}</section> <section className={classes}>{renderTokens()}</section>
</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 { .Combobox.Raid {
box-sizing: border-box; box-sizing: border-box;
min-width: 440px;
.Header { .Header {
background: var(--dialog-bg); background: var(--dialog-bg);
@ -184,6 +185,10 @@
} }
} }
.SelectTrigger.Raid .Value.Empty {
color: var(--text-tertiary);
}
.Filters .SelectTrigger.Raid { .Filters .SelectTrigger.Raid {
& > span { & > span {
overflow: hidden; overflow: hidden;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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