Merge branch 'staging' of github.com:jedmund/hensei-web into staging
This commit is contained in:
commit
9de87abd1e
26 changed files with 384 additions and 369 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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/
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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()}
|
||||||
|
|
|
||||||
|
|
@ -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%;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
.Joined .Input::placeholder {
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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 */
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,115 +172,109 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.ReadOnly {
|
&.Visible {
|
||||||
box-sizing: border-box;
|
display: block;
|
||||||
line-height: 1.4;
|
}
|
||||||
white-space: pre-wrap;
|
|
||||||
|
|
||||||
&.Visible {
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: $font-regular * 1.2;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: $unit;
|
||||||
|
margin-bottom: $unit-2x;
|
||||||
|
}
|
||||||
|
|
||||||
|
.YoutubeWrapper {
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
margin: $unit 0;
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
contain: content;
|
||||||
|
background-position: center center;
|
||||||
|
background-size: cover;
|
||||||
|
cursor: pointer;
|
||||||
|
width: 60%;
|
||||||
|
height: 60%;
|
||||||
|
|
||||||
|
@include breakpoint(tablet) {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* gradient */
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
|
||||||
|
background-position: top;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
height: 60px;
|
||||||
|
padding-bottom: 50px;
|
||||||
|
width: 100%;
|
||||||
|
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
a:hover {
|
/* responsive iframe with a 16:9 aspect ratio
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-regular;
|
|
||||||
line-height: $font-regular * 1.2;
|
|
||||||
white-space: pre-line;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Details {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: $unit;
|
|
||||||
margin-bottom: $unit-2x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.YoutubeWrapper {
|
|
||||||
background-color: var(--card-bg);
|
|
||||||
border-radius: $card-corner;
|
|
||||||
margin: $unit 0;
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
contain: content;
|
|
||||||
background-position: center center;
|
|
||||||
background-size: cover;
|
|
||||||
cursor: pointer;
|
|
||||||
width: 60%;
|
|
||||||
height: 60%;
|
|
||||||
|
|
||||||
@include breakpoint(tablet) {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* gradient */
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAADGCAYAAAAT+OqFAAAAdklEQVQoz42QQQ7AIAgEF/T/D+kbq/RWAlnQyyazA4aoAB4FsBSA/bFjuF1EOL7VbrIrBuusmrt4ZZORfb6ehbWdnRHEIiITaEUKa5EJqUakRSaEYBJSCY2dEstQY7AuxahwXFrvZmWl2rh4JZ07z9dLtesfNj5q0FU3A5ObbwAAAABJRU5ErkJggg==);
|
|
||||||
background-position: top;
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
height: 60px;
|
|
||||||
padding-bottom: 50px;
|
|
||||||
width: 100%;
|
|
||||||
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* responsive iframe with a 16:9 aspect ratio
|
|
||||||
thanks https://css-tricks.com/responsive-iframes/
|
thanks https://css-tricks.com/responsive-iframes/
|
||||||
*/
|
*/
|
||||||
&::after {
|
&::after {
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
padding-bottom: calc(100% / (16 / 9));
|
padding-bottom: calc(100% / (16 / 9));
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover > .PlayerButton {
|
&:hover > .PlayerButton {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
& > iframe {
|
& > iframe {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Play button */
|
/* Play button */
|
||||||
& > .PlayerButton {
|
& > .PlayerButton {
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
background-image: url('/icons/youtube.svg');
|
background-image: url('/icons/youtube.svg');
|
||||||
width: 68px;
|
width: 68px;
|
||||||
height: 68px;
|
height: 68px;
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
transition: all 0.2s cubic-bezier(0, 0, 0.2, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .PlayerButton,
|
& > .PlayerButton,
|
||||||
& > .PlayerButton:before {
|
& > .PlayerButton:before {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate3d(-50%, -50%, 0);
|
transform: translate3d(-50%, -50%, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Post-click styles */
|
/* Post-click styles */
|
||||||
&.lyt-activated {
|
&.lyt-activated {
|
||||||
cursor: unset;
|
cursor: unset;
|
||||||
}
|
}
|
||||||
&.lyt-activated::before,
|
&.lyt-activated::before,
|
||||||
&.lyt-activated > .PlayerButton {
|
&.lyt-activated > .PlayerButton {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
setCurrentSection(props.currentRaid.group.section)
|
if (appState.party.raid && appState.party.raid.group.section > 0)
|
||||||
|
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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -394,12 +394,14 @@ const WeaponModal = ({
|
||||||
{gridWeapon.object.awakenings ? awakeningSelect() : ''}
|
{gridWeapon.object.awakenings ? awakeningSelect() : ''}
|
||||||
</div>
|
</div>
|
||||||
<div className="DialogFooter" ref={footerRef}>
|
<div className="DialogFooter" ref={footerRef}>
|
||||||
<Button
|
<div className="actions">
|
||||||
contained={true}
|
<Button
|
||||||
onClick={updateWeapon}
|
contained={true}
|
||||||
disabled={!formValid}
|
onClick={updateWeapon}
|
||||||
text={t('modals.weapon.buttons.confirm')}
|
disabled={!formValid}
|
||||||
/>
|
text={t('modals.weapon.buttons.confirm')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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": "討伐時間"
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue