Merge branch 'main' into release-notes-1.2.0
This commit is contained in:
commit
b57d0de20e
28 changed files with 1343 additions and 163 deletions
|
|
@ -270,7 +270,7 @@ const CharacterModal = ({
|
|||
<Alert
|
||||
message={
|
||||
<span>
|
||||
<Trans i18nKey="alerts.unsaved_changes.object">
|
||||
<Trans i18nKey="alert.unsaved_changes.object">
|
||||
You will lose all changes to{' '}
|
||||
<strong>{{ objectName: gridCharacter.object.name[locale] }}</strong>{' '}
|
||||
if you continue.
|
||||
|
|
@ -281,9 +281,9 @@ const CharacterModal = ({
|
|||
</span>
|
||||
}
|
||||
open={alertOpen}
|
||||
primaryActionText="Close"
|
||||
primaryActionText={t('alert.unsaved_changes.buttons.confirm')}
|
||||
primaryAction={close}
|
||||
cancelActionText="Nevermind"
|
||||
cancelActionText={t('alert.unsaved_changes.buttons.cancel')}
|
||||
cancelAction={() => setAlertOpen(false)}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@
|
|||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
min-width: 20vw;
|
||||
max-width: 30vw;
|
||||
max-width: 32vw;
|
||||
padding: $unit * 4;
|
||||
|
||||
@include breakpoint(tablet) {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,10 @@
|
|||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&.no-shrink {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&.blended {
|
||||
background: transparent;
|
||||
}
|
||||
|
|
@ -304,6 +308,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
&.notice {
|
||||
background-color: var(--notice-button-bg);
|
||||
color: var(--notice-button-text);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--notice-button-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
&.destructive {
|
||||
background: $error;
|
||||
color: white;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
|
|
@ -20,7 +19,6 @@ import SegmentedControl from '~components/common/SegmentedControl'
|
|||
import Segment from '~components/common/Segment'
|
||||
import SwitchTableField from '~components/common/SwitchTableField'
|
||||
import TableField from '~components/common/TableField'
|
||||
import Textarea from '~components/common/Textarea'
|
||||
|
||||
import capitalizeFirstLetter from '~utils/capitalizeFirstLetter'
|
||||
import type { DetailsObject } from 'types'
|
||||
|
|
@ -384,7 +382,7 @@ const EditPartyModal = ({
|
|||
<Alert
|
||||
message={
|
||||
<span>
|
||||
<Trans i18nKey="alerts.unsaved_changes.party">
|
||||
<Trans i18nKey="alert.unsaved_changes.party">
|
||||
You will lose all changes to your party{' '}
|
||||
<strong>
|
||||
{{
|
||||
|
|
@ -399,9 +397,9 @@ const EditPartyModal = ({
|
|||
</span>
|
||||
}
|
||||
open={alertOpen}
|
||||
primaryActionText="Close"
|
||||
primaryActionText={t('alert.unsaved_changes.buttons.confirm')}
|
||||
primaryAction={close}
|
||||
cancelActionText="Nevermind"
|
||||
cancelActionText={t('alert.unsaved_changes.buttons.cancel')}
|
||||
cancelAction={() => setAlertOpen(false)}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ const Party = (props: Props) => {
|
|||
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 (details.visibility) payload.visibility = details.visibility
|
||||
if (getLocalId()) payload.local_id = getLocalId()
|
||||
|
||||
if (Object.keys(payload).length >= 1) return { party: payload }
|
||||
|
|
@ -292,6 +293,7 @@ const Party = (props: Props) => {
|
|||
appState.party.favorited = team.favorited
|
||||
appState.party.remix = team.remix
|
||||
appState.party.remixes = team.remixes
|
||||
appState.party.visibility = team.visibility
|
||||
appState.party.sourceParty = team.source_party
|
||||
appState.party.created_at = team.created_at
|
||||
appState.party.updated_at = team.updated_at
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Libraries
|
||||
import React, { useState } from 'react'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
|
@ -33,12 +33,14 @@ interface Props {
|
|||
editable: boolean
|
||||
deleteTeamCallback: () => void
|
||||
remixTeamCallback: () => void
|
||||
teamVisibilityCallback: () => void
|
||||
}
|
||||
|
||||
const PartyDropdown = ({
|
||||
editable,
|
||||
deleteTeamCallback,
|
||||
remixTeamCallback,
|
||||
teamVisibilityCallback,
|
||||
}: Props) => {
|
||||
// Localization
|
||||
const { t } = useTranslation('common')
|
||||
|
|
@ -81,6 +83,11 @@ const PartyDropdown = ({
|
|||
|
||||
// Methods: Event handlers
|
||||
|
||||
// Dialogs / Visibility
|
||||
function visibilityCallback() {
|
||||
teamVisibilityCallback()
|
||||
}
|
||||
|
||||
// Alerts / Delete team
|
||||
function openDeleteTeamAlert() {
|
||||
setDeleteAlertOpen(true)
|
||||
|
|
@ -125,6 +132,9 @@ const PartyDropdown = ({
|
|||
const items = (
|
||||
<>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={visibilityCallback}>
|
||||
<span>{t('dropdown.party.visibility')}</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={copyToClipboard}>
|
||||
<span>{t('dropdown.party.copy')}</span>
|
||||
</DropdownMenuItem>
|
||||
|
|
|
|||
|
|
@ -260,16 +260,7 @@ const PartyFooter = (props: Props) => {
|
|||
return partySnapshot?.remixes.map((party, i) => {
|
||||
return (
|
||||
<GridRep
|
||||
id={party.id}
|
||||
shortcode={party.shortcode}
|
||||
name={party.name}
|
||||
createdAt={new Date(party.created_at)}
|
||||
raid={party.raid}
|
||||
grid={party.weapons}
|
||||
user={party.user}
|
||||
favorited={party.favorited}
|
||||
fullAuto={party.full_auto}
|
||||
autoGuard={party.auto_guard}
|
||||
party={party}
|
||||
key={`party-${i}`}
|
||||
loading={false}
|
||||
onClick={goTo}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,58 @@
|
|||
}
|
||||
}
|
||||
|
||||
.notice {
|
||||
align-items: center;
|
||||
background: var(--notice-bg);
|
||||
border-radius: $card-corner;
|
||||
display: flex;
|
||||
gap: $unit-2x;
|
||||
font-size: $font-regular;
|
||||
padding: $unit-4x;
|
||||
overflow: hidden;
|
||||
|
||||
@include breakpoint(small-tablet) {
|
||||
flex-direction: column;
|
||||
gap: $unit;
|
||||
padding: $unit-2x;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--notice-text);
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-items: center;
|
||||
background-color: var(--notice-button-bg);
|
||||
border-radius: $full-corner;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
height: $unit-6x;
|
||||
width: $unit-6x;
|
||||
flex-shrink: 0;
|
||||
|
||||
svg {
|
||||
fill: var(--notice-text);
|
||||
width: $unit-3x;
|
||||
height: $unit-3x;
|
||||
}
|
||||
}
|
||||
|
||||
.buttons {
|
||||
justify-content: flex-end;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
gap: $unit;
|
||||
|
||||
@include breakpoint(small-tablet) {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
|
|
|
|||
|
|
@ -19,10 +19,14 @@ import { formatTimeAgo } from '~utils/timeAgo'
|
|||
|
||||
import RemixTeamAlert from '~components/dialogs/RemixTeamAlert'
|
||||
import RemixedToast from '~components/toasts/RemixedToast'
|
||||
import PartyVisibilityDialog from '~components/party/PartyVisibilityDialog'
|
||||
import UrlCopiedToast from '~components/toasts/UrlCopiedToast'
|
||||
|
||||
import EditIcon from '~public/icons/Edit.svg'
|
||||
import RemixIcon from '~public/icons/Remix.svg'
|
||||
import SaveIcon from '~public/icons/Save.svg'
|
||||
import PrivateIcon from '~public/icons/Private.svg'
|
||||
import UnlistedIcon from '~public/icons/Unlisted.svg'
|
||||
|
||||
import type { DetailsObject } from 'types'
|
||||
|
||||
|
|
@ -50,8 +54,10 @@ const PartyHeader = (props: Props) => {
|
|||
|
||||
// State: Component
|
||||
const [detailsOpen, setDetailsOpen] = useState(false)
|
||||
const [copyToastOpen, setCopyToastOpen] = useState(false)
|
||||
const [remixAlertOpen, setRemixAlertOpen] = useState(false)
|
||||
const [remixToastOpen, setRemixToastOpen] = useState(false)
|
||||
const [visibilityDialogOpen, setVisibilityDialogOpen] = useState(false)
|
||||
|
||||
const userClass = classNames({
|
||||
[styles.user]: true,
|
||||
|
|
@ -122,12 +128,29 @@ const PartyHeader = (props: Props) => {
|
|||
setDetailsOpen(open)
|
||||
}
|
||||
|
||||
// Dialogs: Visibility
|
||||
function visibilityDialogCallback() {
|
||||
setVisibilityDialogOpen(true)
|
||||
}
|
||||
|
||||
function handleVisibilityDialogChange(open: boolean) {
|
||||
setVisibilityDialogOpen(open)
|
||||
}
|
||||
|
||||
// Actions: Remix team
|
||||
function remixTeamCallback() {
|
||||
setRemixToastOpen(true)
|
||||
props.remixCallback()
|
||||
}
|
||||
|
||||
// Actions: Copy URL
|
||||
function copyToClipboard() {
|
||||
if (router.asPath.split('/')[1] === 'p') {
|
||||
navigator.clipboard.writeText(window.location.href)
|
||||
setCopyToastOpen(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Alerts: Remix team
|
||||
function openRemixTeamAlert() {
|
||||
setRemixAlertOpen(true)
|
||||
|
|
@ -146,6 +169,15 @@ const PartyHeader = (props: Props) => {
|
|||
setRemixToastOpen(false)
|
||||
}
|
||||
|
||||
// Toasts / Copy URL
|
||||
function handleCopyToastOpenChanged(open: boolean) {
|
||||
setCopyToastOpen(!open)
|
||||
}
|
||||
|
||||
function handleCopyToastCloseClicked() {
|
||||
setCopyToastOpen(false)
|
||||
}
|
||||
|
||||
// Rendering
|
||||
|
||||
const userBlock = (username?: string, picture?: string, element?: string) => {
|
||||
|
|
@ -298,6 +330,50 @@ const PartyHeader = (props: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
// Render: Notice
|
||||
const unlistedNotice = (
|
||||
<div className={styles.notice}>
|
||||
<div className={styles.icon}>
|
||||
<UnlistedIcon />
|
||||
</div>
|
||||
<p>{t('party.notices.unlisted')}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Button
|
||||
bound={true}
|
||||
className="notice no-shrink"
|
||||
key="copy_link"
|
||||
text={t('party.notices.buttons.copy_link')}
|
||||
onClick={copyToClipboard}
|
||||
/>
|
||||
<Button
|
||||
bound={true}
|
||||
className="notice no-shrink"
|
||||
key="change_visibility"
|
||||
text={t('party.notices.buttons.change_visibility')}
|
||||
onClick={() => handleVisibilityDialogChange(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const privateNotice = (
|
||||
<div className={styles.notice}>
|
||||
<div className={styles.icon}>
|
||||
<PrivateIcon />
|
||||
</div>
|
||||
<p>{t('party.notices.private')}</p>
|
||||
<div className={styles.buttons}>
|
||||
<Button
|
||||
bound={true}
|
||||
className="notice"
|
||||
key="change_visibility"
|
||||
text={t('party.notices.buttons.change_visibility')}
|
||||
onClick={() => handleVisibilityDialogChange(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
// Render: Buttons
|
||||
const saveButton = () => {
|
||||
return (
|
||||
|
|
@ -358,6 +434,8 @@ const PartyHeader = (props: Props) => {
|
|||
return (
|
||||
<>
|
||||
<header className={styles.wrapper}>
|
||||
{party.visibility == 2 && unlistedNotice}
|
||||
{party.visibility == 3 && privateNotice}
|
||||
<section className={styles.info}>
|
||||
<div className={styles.left}>
|
||||
<div className={styles.header}>
|
||||
|
|
@ -399,6 +477,7 @@ const PartyHeader = (props: Props) => {
|
|||
editable={props.editable}
|
||||
deleteTeamCallback={props.deleteCallback}
|
||||
remixTeamCallback={props.remixCallback}
|
||||
teamVisibilityCallback={visibilityDialogCallback}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -412,6 +491,13 @@ const PartyHeader = (props: Props) => {
|
|||
<section className={styles.tokens}>{renderTokens()}</section>
|
||||
</header>
|
||||
|
||||
<PartyVisibilityDialog
|
||||
open={visibilityDialogOpen}
|
||||
value={party.visibility as 1 | 2 | 3}
|
||||
onOpenChange={handleVisibilityDialogChange}
|
||||
updateParty={props.updateCallback}
|
||||
/>
|
||||
|
||||
<RemixTeamAlert
|
||||
creator={props.editable}
|
||||
name={partySnapshot.name ? partySnapshot.name : t('no_title')}
|
||||
|
|
@ -426,6 +512,12 @@ const PartyHeader = (props: Props) => {
|
|||
onOpenChange={handleRemixToastOpenChanged}
|
||||
onCloseClick={handleRemixToastCloseClicked}
|
||||
/>
|
||||
|
||||
<UrlCopiedToast
|
||||
open={copyToastOpen}
|
||||
onOpenChange={handleCopyToastOpenChanged}
|
||||
onCloseClick={handleCopyToastCloseClicked}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
83
components/party/PartyVisibilityDialog/index.module.scss
Normal file
83
components/party/PartyVisibilityDialog/index.module.scss
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-4x;
|
||||
padding: 0 $unit-4x $unit-2x;
|
||||
|
||||
.description {
|
||||
color: var(--text-primary);
|
||||
font-size: $font-regular;
|
||||
}
|
||||
|
||||
.radioGroup {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-2x;
|
||||
|
||||
.radioSet {
|
||||
display: flex;
|
||||
gap: $unit;
|
||||
|
||||
.radioItem {
|
||||
align-items: center;
|
||||
background: var(--radio-button-bg);
|
||||
border-radius: $full-corner;
|
||||
border: none;
|
||||
display: flex;
|
||||
border: 2px solid transparent;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
height: $unit-4x;
|
||||
width: $unit-4x;
|
||||
min-height: $unit-4x;
|
||||
min-width: $unit-4x;
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--radio-active-bg);
|
||||
|
||||
&:hover {
|
||||
outline-color: var(--radio-active-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
[data-state='checked'] {
|
||||
background-color: var(--radio-active-bg);
|
||||
border-radius: $full-corner;
|
||||
display: block;
|
||||
height: $unit-2x;
|
||||
width: $unit-2x;
|
||||
}
|
||||
|
||||
&[data-state='checked']:hover [data-state='checked'] {
|
||||
background-color: var(--radio-active-bg-hover);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--radio-button-bg-hover);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $unit-half;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: var(--text-primary);
|
||||
font-size: $font-regular;
|
||||
font-weight: $bold;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--text-tertiary);
|
||||
font-size: $font-small;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
303
components/party/PartyVisibilityDialog/index.tsx
Normal file
303
components/party/PartyVisibilityDialog/index.tsx
Normal file
|
|
@ -0,0 +1,303 @@
|
|||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import { useSnapshot } from 'valtio'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import * as RadioGroup from '@radix-ui/react-radio-group'
|
||||
import Alert from '~components/common/Alert'
|
||||
import Button from '~components/common/Button'
|
||||
import { Dialog, DialogTrigger } from '~components/common/Dialog'
|
||||
import DialogHeader from '~components/common/DialogHeader'
|
||||
import DialogFooter from '~components/common/DialogFooter'
|
||||
import DialogContent from '~components/common/DialogContent'
|
||||
|
||||
import type { DetailsObject } from 'types'
|
||||
import type { DialogProps } from '@radix-ui/react-dialog'
|
||||
|
||||
import { appState } from '~utils/appState'
|
||||
|
||||
import styles from './index.module.scss'
|
||||
|
||||
interface Props extends DialogProps {
|
||||
open: boolean
|
||||
value: 1 | 2 | 3
|
||||
onOpenChange?: (open: boolean) => void
|
||||
updateParty: (details: DetailsObject) => Promise<any>
|
||||
}
|
||||
|
||||
const EditPartyModal = ({
|
||||
open,
|
||||
value,
|
||||
updateParty,
|
||||
onOpenChange,
|
||||
...props
|
||||
}: Props) => {
|
||||
// Set up translation
|
||||
const { t } = useTranslation('common')
|
||||
|
||||
// Set up reactive state
|
||||
const { party } = useSnapshot(appState)
|
||||
|
||||
// Refs
|
||||
const headerRef = React.createRef<HTMLDivElement>()
|
||||
const topContainerRef = React.createRef<HTMLDivElement>()
|
||||
const footerRef = React.createRef<HTMLDivElement>()
|
||||
const radioItemRef = [
|
||||
React.createRef<HTMLButtonElement>(),
|
||||
React.createRef<HTMLButtonElement>(),
|
||||
React.createRef<HTMLButtonElement>(),
|
||||
]
|
||||
|
||||
// States: Component
|
||||
const [alertOpen, setAlertOpen] = useState(false)
|
||||
const [errors, setErrors] = useState<{ [key: string]: string }>({
|
||||
name: '',
|
||||
description: '',
|
||||
})
|
||||
|
||||
// States: Data
|
||||
const [visibility, setVisibility] = useState(1)
|
||||
|
||||
// Hooks
|
||||
useEffect(() => {
|
||||
setVisibility(party.visibility)
|
||||
}, [value])
|
||||
|
||||
// Methods: Event handlers (Dialog)
|
||||
function handleOpenChange() {
|
||||
if (hasBeenModified() && open) {
|
||||
setAlertOpen(true)
|
||||
} else if (!hasBeenModified() && open) {
|
||||
close()
|
||||
} else {
|
||||
if (onOpenChange) onOpenChange(true)
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
setAlertOpen(false)
|
||||
setVisibility(party.visibility)
|
||||
if (onOpenChange) onOpenChange(false)
|
||||
}
|
||||
|
||||
function onEscapeKeyDown(event: KeyboardEvent) {
|
||||
event.preventDefault()
|
||||
handleOpenChange()
|
||||
}
|
||||
|
||||
function onOpenAutoFocus(event: Event) {
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
// Methods: Event handlers (Fields)
|
||||
function handleValueChange(value: string) {
|
||||
const newVisibility = parseInt(value)
|
||||
setVisibility(newVisibility)
|
||||
}
|
||||
|
||||
// Handlers
|
||||
function handleScroll(event: React.UIEvent<HTMLDivElement, UIEvent>) {
|
||||
const scrollTop = event.currentTarget.scrollTop
|
||||
const scrollHeight = event.currentTarget.scrollHeight
|
||||
const clientHeight = event.currentTarget.clientHeight
|
||||
|
||||
if (topContainerRef && topContainerRef.current)
|
||||
manipulateHeaderShadow(topContainerRef.current, scrollTop)
|
||||
|
||||
if (footerRef && footerRef.current)
|
||||
manipulateFooterShadow(
|
||||
footerRef.current,
|
||||
scrollTop,
|
||||
scrollHeight,
|
||||
clientHeight
|
||||
)
|
||||
}
|
||||
|
||||
function manipulateHeaderShadow(header: HTMLDivElement, scrollTop: number) {
|
||||
const boxShadowBase = '0 2px 8px'
|
||||
const maxValue = 50
|
||||
|
||||
if (scrollTop >= 0) {
|
||||
const input = scrollTop > maxValue ? maxValue : scrollTop
|
||||
|
||||
const boxShadowOpacity = mapRange(input, 0, maxValue, 0.0, 0.16)
|
||||
const borderOpacity = mapRange(input, 0, maxValue, 0.0, 0.24)
|
||||
|
||||
header.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, ${boxShadowOpacity})`
|
||||
header.style.borderBottomColor = `rgba(0, 0, 0, ${borderOpacity})`
|
||||
}
|
||||
}
|
||||
|
||||
function manipulateFooterShadow(
|
||||
footer: HTMLDivElement,
|
||||
scrollTop: number,
|
||||
scrollHeight: number,
|
||||
clientHeight: number
|
||||
) {
|
||||
const boxShadowBase = '0 -2px 8px'
|
||||
const minValue = scrollHeight - 200
|
||||
const currentScroll = scrollTop + clientHeight
|
||||
|
||||
if (currentScroll >= minValue) {
|
||||
const input = currentScroll < minValue ? minValue : currentScroll
|
||||
|
||||
const boxShadowOpacity = mapRange(
|
||||
input,
|
||||
minValue,
|
||||
scrollHeight,
|
||||
0.16,
|
||||
0.0
|
||||
)
|
||||
const borderOpacity = mapRange(input, minValue, scrollHeight, 0.24, 0.0)
|
||||
|
||||
footer.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, ${boxShadowOpacity})`
|
||||
footer.style.borderTopColor = `rgba(0, 0, 0, ${borderOpacity})`
|
||||
}
|
||||
}
|
||||
|
||||
const calculateFooterShadow = debounce(() => {
|
||||
const boxShadowBase = '0 -2px 8px'
|
||||
const scrollable = document.querySelector(`.${styles.scrollable}`)
|
||||
const footer = footerRef
|
||||
|
||||
if (footer && footer.current) {
|
||||
if (scrollable) {
|
||||
if (scrollable.clientHeight >= scrollable.scrollHeight) {
|
||||
footer.current.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, 0)`
|
||||
footer.current.style.borderTopColor = `rgba(0, 0, 0, 0)`
|
||||
} else {
|
||||
footer.current.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, 0.16)`
|
||||
footer.current.style.borderTopColor = `rgba(0, 0, 0, 0.24)`
|
||||
}
|
||||
} else {
|
||||
footer.current.style.boxShadow = `${boxShadowBase} rgba(0, 0, 0, 0)`
|
||||
footer.current.style.borderTopColor = `rgba(0, 0, 0, 0)`
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', calculateFooterShadow)
|
||||
calculateFooterShadow()
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', calculateFooterShadow)
|
||||
}
|
||||
}, [calculateFooterShadow])
|
||||
|
||||
function mapRange(
|
||||
value: number,
|
||||
low1: number,
|
||||
high1: number,
|
||||
low2: number,
|
||||
high2: number
|
||||
) {
|
||||
return low2 + ((high2 - low2) * (value - low1)) / (high1 - low1)
|
||||
}
|
||||
|
||||
// Methods: Modification checking
|
||||
function hasBeenModified() {
|
||||
return visibility !== party.visibility
|
||||
}
|
||||
|
||||
// Methods: Data methods
|
||||
async function updateDetails(event: React.MouseEvent) {
|
||||
const details: DetailsObject = {
|
||||
visibility: visibility,
|
||||
}
|
||||
|
||||
await updateParty(details)
|
||||
if (onOpenChange) onOpenChange(false)
|
||||
}
|
||||
|
||||
// Methods: Rendering methods
|
||||
function renderRadioItem(value: string, label: string) {
|
||||
return (
|
||||
<div className={styles.radioSet}>
|
||||
<RadioGroup.Item
|
||||
className={styles.radioItem}
|
||||
value={value}
|
||||
id={label}
|
||||
tabIndex={parseInt(value)}
|
||||
ref={radioItemRef[parseInt(value)]}
|
||||
>
|
||||
<RadioGroup.Indicator className={styles.radioIndicator} />
|
||||
</RadioGroup.Item>
|
||||
<label htmlFor={label}>
|
||||
<h4>{t(`modals.team_visibility.options.${label}`)}</h4>
|
||||
<p>{t(`modals.team_visibility.descriptions.${label}`)}</p>
|
||||
</label>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const confirmationAlert = (
|
||||
<Alert
|
||||
message={t('modals.team_visibility.alerts.unsaved_changes.message')}
|
||||
open={alertOpen}
|
||||
primaryActionText={t(
|
||||
'modals.team_visibility.alerts.unsaved_changes.buttons.confirm'
|
||||
)}
|
||||
primaryAction={close}
|
||||
cancelActionText={t(
|
||||
'modals.team_visibility.alerts.unsaved_changes.buttons.cancel'
|
||||
)}
|
||||
cancelAction={() => setAlertOpen(false)}
|
||||
/>
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{confirmationAlert}
|
||||
<Dialog open={open} onOpenChange={handleOpenChange}>
|
||||
<DialogTrigger asChild>{props.children}</DialogTrigger>
|
||||
<DialogContent
|
||||
className="changeVisibility"
|
||||
onEscapeKeyDown={onEscapeKeyDown}
|
||||
onOpenAutoFocus={onOpenAutoFocus}
|
||||
>
|
||||
<DialogHeader
|
||||
title={t('modals.team_visibility.title')}
|
||||
ref={headerRef}
|
||||
/>
|
||||
|
||||
<div className={styles.content}>
|
||||
<p className={styles.description}>
|
||||
{t('modals.team_visibility.description')}
|
||||
</p>
|
||||
<RadioGroup.Root
|
||||
className={styles.radioGroup}
|
||||
defaultValue={`${visibility}`}
|
||||
aria-label={t('modals.team_visibility.label')}
|
||||
onValueChange={handleValueChange}
|
||||
>
|
||||
{renderRadioItem('1', 'public')}
|
||||
{renderRadioItem('2', 'unlisted')}
|
||||
{renderRadioItem('3', 'private')}
|
||||
</RadioGroup.Root>
|
||||
</div>
|
||||
|
||||
<DialogFooter
|
||||
ref={footerRef}
|
||||
rightElements={[
|
||||
<Button
|
||||
bound={true}
|
||||
onClick={() => onOpenChange && onOpenChange(false)}
|
||||
key="cancel"
|
||||
text={t('buttons.cancel')}
|
||||
/>,
|
||||
<Button
|
||||
bound={true}
|
||||
key="confirm"
|
||||
onClick={updateDetails}
|
||||
text={t('modals.team_visibility.buttons.confirm')}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditPartyModal
|
||||
|
|
@ -47,32 +47,32 @@
|
|||
}
|
||||
|
||||
&.fire {
|
||||
background: var(--fire-hover-bg);
|
||||
background: var(--fire-portrait-bg);
|
||||
border-color: var(--fire-bg);
|
||||
}
|
||||
|
||||
&.water {
|
||||
background: var(--water-hover-bg);
|
||||
background: var(--water-portrait-bg);
|
||||
border-color: var(--water-bg);
|
||||
}
|
||||
|
||||
&.wind {
|
||||
background: var(--wind-hover-bg);
|
||||
background: var(--wind-portrait-bg);
|
||||
border-color: var(--wind-bg);
|
||||
}
|
||||
|
||||
&.earth {
|
||||
background: var(--earth-hover-bg);
|
||||
background: var(--earth-portrait-bg);
|
||||
border-color: var(--earth-bg);
|
||||
}
|
||||
|
||||
&.light {
|
||||
background: var(--light-hover-bg);
|
||||
background: var(--light-portrait-bg);
|
||||
border-color: var(--light-bg);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background: var(--dark-hover-bg);
|
||||
background: var(--dark-portrait-bg);
|
||||
border-color: var(--dark-bg);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@
|
|||
display: grid;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
gap: $unit;
|
||||
padding: $unit-2x;
|
||||
padding: $unit-2x $unit-2x 0 $unit-2x;
|
||||
min-width: 320px;
|
||||
width: 100%;
|
||||
opacity: 1;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
padding-bottom: $unit-2x;
|
||||
}
|
||||
|
||||
&.visible {
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
opacity: 1;
|
||||
|
|
@ -29,6 +33,10 @@
|
|||
text-decoration: none;
|
||||
}
|
||||
|
||||
.indicators {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.weaponGrid {
|
||||
cursor: pointer;
|
||||
|
||||
|
|
@ -46,7 +54,70 @@
|
|||
}
|
||||
}
|
||||
|
||||
& > .weaponGrid {
|
||||
.gridContainer {
|
||||
aspect-ratio: 2/0.95;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.characterGrid {
|
||||
aspect-ratio: 2/0.95;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.protagonist {
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
|
||||
&.fire {
|
||||
background: var(--fire-portrait-bg);
|
||||
border-color: var(--fire-bg);
|
||||
}
|
||||
|
||||
&.water {
|
||||
background: var(--water-portrait-bg);
|
||||
border-color: var(--water-bg);
|
||||
}
|
||||
|
||||
&.wind {
|
||||
background: var(--wind-portrait-bg);
|
||||
border-color: var(--wind-bg);
|
||||
}
|
||||
|
||||
&.earth {
|
||||
background: var(--earth-portrait-bg);
|
||||
border-color: var(--earth-bg);
|
||||
}
|
||||
|
||||
&.light {
|
||||
background: var(--light-portrait-bg);
|
||||
border-color: var(--light-bg);
|
||||
}
|
||||
|
||||
&.dark {
|
||||
background: var(--dark-portrait-bg);
|
||||
border-color: var(--dark-bg);
|
||||
}
|
||||
|
||||
&.empty {
|
||||
background: var(--card-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.grid {
|
||||
background: var(--background);
|
||||
border-radius: $item-corner-small;
|
||||
aspect-ratio: 69/142;
|
||||
list-style: none;
|
||||
height: calc(100% - $unit-half);
|
||||
|
||||
img {
|
||||
border-radius: $item-corner-small;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.weaponGrid {
|
||||
aspect-ratio: 2/0.95;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 3.36fr; /* left column takes up 1 fraction, right column takes up 3 fractions */
|
||||
|
|
@ -54,7 +125,7 @@
|
|||
|
||||
.weapon {
|
||||
background: var(--unit-bg);
|
||||
border-radius: 4px;
|
||||
border-radius: $item-corner-small;
|
||||
}
|
||||
|
||||
.mainhand.weapon {
|
||||
|
|
@ -91,6 +162,51 @@
|
|||
}
|
||||
}
|
||||
|
||||
.summonGrid {
|
||||
aspect-ratio: 2/0.94;
|
||||
display: flex;
|
||||
gap: $unit;
|
||||
justify-content: space-between;
|
||||
|
||||
.summon,
|
||||
.mainSummon {
|
||||
background: var(--background);
|
||||
border-radius: $item-corner-small;
|
||||
|
||||
img {
|
||||
border-radius: $item-corner-small;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.mainSummon {
|
||||
aspect-ratio: 56/97;
|
||||
display: grid;
|
||||
grid-column: 1 / 2; /* spans one column */
|
||||
}
|
||||
|
||||
.summons {
|
||||
display: grid; /* make the right-images container a grid */
|
||||
grid-template-columns: repeat(
|
||||
2,
|
||||
1fr
|
||||
); /* create 3 columns, each taking up 1 fraction */
|
||||
grid-template-rows: repeat(
|
||||
2,
|
||||
1fr
|
||||
); /* create 3 rows, each taking up 1 fraction */
|
||||
gap: $unit;
|
||||
aspect-ratio: 83/100;
|
||||
// column-gap: $unit;
|
||||
// row-gap: $unit-2x;
|
||||
}
|
||||
|
||||
.summon {
|
||||
aspect-ratio: 184 / 138;
|
||||
display: grid;
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -104,6 +220,7 @@
|
|||
padding-bottom: 1px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-height: 24px;
|
||||
max-width: 258px; // Can we not do this?
|
||||
|
||||
&.empty {
|
||||
|
|
@ -124,6 +241,18 @@
|
|||
gap: calc($unit / 2);
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: $unit * 2.5;
|
||||
height: $unit * 2.5;
|
||||
|
||||
svg {
|
||||
fill: var(--text-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
button svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
|
|
@ -157,6 +286,7 @@
|
|||
}
|
||||
|
||||
time {
|
||||
line-height: 18px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
|
@ -234,4 +364,41 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.indicators {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: $unit;
|
||||
margin-top: $unit * -1;
|
||||
margin-bottom: $unit-fourth;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
|
||||
@include breakpoint(phone) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li {
|
||||
flex-grow: 1;
|
||||
text-indent: -9999px;
|
||||
padding: $unit 0;
|
||||
|
||||
.indicator {
|
||||
transition: background-color 0.12s ease-in-out;
|
||||
height: $unit;
|
||||
border-radius: $unit-half;
|
||||
background-color: var(--button-contained-bg-hover);
|
||||
}
|
||||
|
||||
span {
|
||||
text-indent: -9999px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
&:hover .indicator,
|
||||
&.active .indicator {
|
||||
background-color: var(--text-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,29 +10,24 @@ import { accountState } from '~utils/accountState'
|
|||
import { formatTimeAgo } from '~utils/timeAgo'
|
||||
|
||||
import Button from '~components/common/Button'
|
||||
import Tooltip from '~components/common/Tooltip'
|
||||
|
||||
import SaveIcon from '~public/icons/Save.svg'
|
||||
import PrivateIcon from '~public/icons/Private.svg'
|
||||
import UnlistedIcon from '~public/icons/Unlisted.svg'
|
||||
import ShieldIcon from '~public/icons/Shield.svg'
|
||||
import styles from './index.module.scss'
|
||||
|
||||
interface Props {
|
||||
shortcode: string
|
||||
id: string
|
||||
name: string
|
||||
raid: Raid
|
||||
grid: GridWeapon[]
|
||||
user?: User
|
||||
fullAuto: boolean
|
||||
autoGuard: boolean
|
||||
favorited: boolean
|
||||
party: Party
|
||||
loading: boolean
|
||||
createdAt: Date
|
||||
onClick: (shortcode: string) => void
|
||||
onSave?: (partyId: string, favorited: boolean) => void
|
||||
}
|
||||
|
||||
const GridRep = (props: Props) => {
|
||||
const GridRep = ({ party, loading, onClick, onSave }: Props) => {
|
||||
const numWeapons: number = 9
|
||||
const numSummons: number = 6
|
||||
|
||||
const { account } = useSnapshot(accountState)
|
||||
|
||||
|
|
@ -42,27 +37,42 @@ const GridRep = (props: Props) => {
|
|||
router.locale && ['en', 'ja'].includes(router.locale) ? router.locale : 'en'
|
||||
|
||||
const [visible, setVisible] = useState(false)
|
||||
const [currentView, setCurrentView] = useState<
|
||||
'characters' | 'weapons' | 'summons'
|
||||
>('weapons')
|
||||
|
||||
const [mainhand, setMainhand] = useState<Weapon>()
|
||||
const [weapons, setWeapons] = useState<GridArray<Weapon>>({})
|
||||
const [grid, setGrid] = useState<GridArray<GridWeapon>>({})
|
||||
const [weaponGrid, setWeaponGrid] = useState<GridArray<GridWeapon>>({})
|
||||
const [characters, setCharacters] = useState<GridArray<Character>>({})
|
||||
const [characterGrid, setCharacterGrid] = useState<GridArray<GridCharacter>>(
|
||||
{}
|
||||
)
|
||||
const [mainSummon, setMainSummon] = useState<GridSummon>()
|
||||
const [friendSummon, setFriendSummon] = useState<GridSummon>()
|
||||
const [summons, setSummons] = useState<GridArray<Summon>>({})
|
||||
const [summonGrid, setSummonGrid] = useState<GridArray<GridSummon>>({})
|
||||
|
||||
const gridRepStyles = classNames({
|
||||
// Style construction
|
||||
|
||||
const gridRepClasses = classNames({
|
||||
[styles.gridRep]: true,
|
||||
[styles.visible]: visible,
|
||||
[styles.hidden]: !visible,
|
||||
})
|
||||
|
||||
const titleClass = classNames({
|
||||
empty: !props.name,
|
||||
empty: !party.name,
|
||||
})
|
||||
|
||||
const raidClass = classNames({
|
||||
[styles.raid]: true,
|
||||
[styles.empty]: !props.raid,
|
||||
[styles.empty]: !party.raid,
|
||||
})
|
||||
|
||||
const userClass = classNames({
|
||||
[styles.user]: true,
|
||||
[styles.empty]: !props.user,
|
||||
[styles.empty]: !party.user,
|
||||
})
|
||||
|
||||
const mainhandClasses = classNames({
|
||||
|
|
@ -75,8 +85,22 @@ const GridRep = (props: Props) => {
|
|||
[styles.grid]: true,
|
||||
})
|
||||
|
||||
const protagonistClasses = classNames({
|
||||
[styles.protagonist]: true,
|
||||
[styles.grid]: true,
|
||||
[styles[`${numberToElement()}`]]: true,
|
||||
[styles.empty]: !party.job || party.job.id === '-1',
|
||||
})
|
||||
|
||||
const characterClasses = classNames({
|
||||
[styles.character]: true,
|
||||
[styles.grid]: true,
|
||||
})
|
||||
|
||||
// Hooks
|
||||
|
||||
useEffect(() => {
|
||||
if (props.loading) {
|
||||
if (loading) {
|
||||
setVisible(false)
|
||||
} else {
|
||||
const timeout = setTimeout(() => {
|
||||
|
|
@ -84,7 +108,7 @@ const GridRep = (props: Props) => {
|
|||
}, 150)
|
||||
return () => clearTimeout(timeout)
|
||||
}
|
||||
}, [props.loading])
|
||||
}, [loading])
|
||||
|
||||
useEffect(() => {
|
||||
setVisible(false) // Trigger fade out
|
||||
|
|
@ -99,7 +123,7 @@ const GridRep = (props: Props) => {
|
|||
const gridWeapons = Array(numWeapons)
|
||||
|
||||
let foundMainhand = false
|
||||
for (const [key, value] of Object.entries(props.grid)) {
|
||||
for (const [key, value] of Object.entries(party.weapons)) {
|
||||
if (value.position == -1) {
|
||||
setMainhand(value.object)
|
||||
foundMainhand = true
|
||||
|
|
@ -114,18 +138,74 @@ const GridRep = (props: Props) => {
|
|||
}
|
||||
|
||||
setWeapons(newWeapons)
|
||||
setGrid(gridWeapons)
|
||||
}, [props.grid])
|
||||
setWeaponGrid(gridWeapons)
|
||||
}, [party])
|
||||
|
||||
function navigate() {
|
||||
props.onClick(props.shortcode)
|
||||
useEffect(() => {
|
||||
const newCharacters = Array(3)
|
||||
const gridCharacters = Array(3)
|
||||
|
||||
if (party.characters) {
|
||||
for (const [key, value] of Object.entries(party.characters)) {
|
||||
if (value.position != null) {
|
||||
newCharacters[value.position] = value.object
|
||||
gridCharacters[value.position] = value
|
||||
}
|
||||
}
|
||||
|
||||
setCharacters(newCharacters)
|
||||
setCharacterGrid(gridCharacters)
|
||||
}
|
||||
}, [party])
|
||||
|
||||
useEffect(() => {
|
||||
const newSummons = Array(numSummons)
|
||||
const gridSummons = Array(numSummons)
|
||||
|
||||
if (party.summons) {
|
||||
for (const [key, value] of Object.entries(party.summons)) {
|
||||
if (value.main) {
|
||||
setMainSummon(value)
|
||||
} else if (value.friend) {
|
||||
setFriendSummon(value)
|
||||
} else if (!value.main && !value.friend && value.position != null) {
|
||||
newSummons[value.position] = value.object
|
||||
gridSummons[value.position] = value
|
||||
}
|
||||
}
|
||||
|
||||
setSummons(newSummons)
|
||||
setSummonGrid(gridSummons)
|
||||
}
|
||||
}, [party])
|
||||
|
||||
// Convert element to string
|
||||
function numberToElement() {
|
||||
switch (mainhand?.element || weaponGrid[0]?.element) {
|
||||
case 1:
|
||||
return 'wind'
|
||||
case 2:
|
||||
return 'fire'
|
||||
case 3:
|
||||
return 'water'
|
||||
case 4:
|
||||
return 'earth'
|
||||
case 5:
|
||||
return 'dark'
|
||||
case 6:
|
||||
return 'light'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
|
||||
// Methods: Image generation
|
||||
|
||||
function generateMainhandImage() {
|
||||
let url = ''
|
||||
|
||||
if (mainhand) {
|
||||
const weapon = Object.values(props.grid).find(
|
||||
const weapon = Object.values(party.weapons).find(
|
||||
(w) => w && w.object.id === mainhand.id
|
||||
)
|
||||
|
||||
|
|
@ -136,18 +216,18 @@ const GridRep = (props: Props) => {
|
|||
}
|
||||
}
|
||||
|
||||
return mainhand && props.grid[0] ? (
|
||||
return mainhand && party.weapons[0] ? (
|
||||
<img alt={mainhand.name[locale]} src={url} />
|
||||
) : (
|
||||
''
|
||||
)
|
||||
}
|
||||
|
||||
function generateGridImage(position: number) {
|
||||
function generateWeaponGridImage(position: number) {
|
||||
let url = ''
|
||||
|
||||
const weapon = weapons[position]
|
||||
const gridWeapon = grid[position]
|
||||
const gridWeapon = weaponGrid[position]
|
||||
|
||||
if (weapon && gridWeapon) {
|
||||
if (weapon.element == 0 && gridWeapon.element) {
|
||||
|
|
@ -164,19 +244,163 @@ const GridRep = (props: Props) => {
|
|||
)
|
||||
}
|
||||
|
||||
function generateMCImage() {
|
||||
let source = ''
|
||||
|
||||
if (party.job) {
|
||||
const slug = party.job.name.en.replaceAll(' ', '-').toLowerCase()
|
||||
const gender = party.user?.gender == 1 ? 'b' : 'a'
|
||||
source = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/job-portraits/${slug}_${gender}.png`
|
||||
}
|
||||
|
||||
return (
|
||||
party.job &&
|
||||
party.job.id !== '-1' && (
|
||||
<img alt={party.job ? party.job?.name[locale] : ''} src={source} />
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function generateCharacterGridImage(position: number) {
|
||||
let url = ''
|
||||
|
||||
const gridCharacter = characterGrid[position]
|
||||
const character = characters[position]
|
||||
|
||||
if (character && gridCharacter) {
|
||||
// Change the image based on the uncap level
|
||||
let suffix = '01'
|
||||
if (gridCharacter.transcendence_step > 0) suffix = '04'
|
||||
else if (gridCharacter.uncap_level >= 5) suffix = '03'
|
||||
else if (gridCharacter.uncap_level > 2) suffix = '02'
|
||||
|
||||
if (gridCharacter.object.granblue_id === '3030182000') {
|
||||
let element = 1
|
||||
if (mainhand && mainhand.element) {
|
||||
element = mainhand.element
|
||||
}
|
||||
|
||||
suffix = `${suffix}_0${element}`
|
||||
}
|
||||
|
||||
const url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/character-main/${character.granblue_id}_${suffix}.jpg`
|
||||
|
||||
return (
|
||||
characters[position] && (
|
||||
<img alt={characters[position]?.name[locale]} src={url} />
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function generateMainSummonImage(position: 'main' | 'friend') {
|
||||
let url = ''
|
||||
|
||||
const upgradedSummons = [
|
||||
'2040094000',
|
||||
'2040100000',
|
||||
'2040080000',
|
||||
'2040098000',
|
||||
'2040090000',
|
||||
'2040084000',
|
||||
'2040003000',
|
||||
'2040056000',
|
||||
'2040020000',
|
||||
'2040034000',
|
||||
'2040028000',
|
||||
'2040027000',
|
||||
'2040046000',
|
||||
'2040047000',
|
||||
]
|
||||
|
||||
const summon = position === 'main' ? mainSummon : friendSummon
|
||||
|
||||
if (summon) {
|
||||
// Change the image based on the uncap level
|
||||
let suffix = ''
|
||||
if (summon.object.uncap.xlb && summon.uncap_level == 6) {
|
||||
if (summon.transcendence_step >= 1 && summon.transcendence_step < 5) {
|
||||
suffix = '_03'
|
||||
} else if (summon.transcendence_step === 5) {
|
||||
suffix = '_04'
|
||||
}
|
||||
} else if (
|
||||
upgradedSummons.indexOf(summon.object.granblue_id.toString()) != -1 &&
|
||||
summon.uncap_level == 5
|
||||
) {
|
||||
suffix = '_02'
|
||||
}
|
||||
|
||||
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-main/${summon.object.granblue_id}${suffix}.jpg`
|
||||
}
|
||||
|
||||
return summon && <img alt={summon.object.name[locale]} src={url} />
|
||||
}
|
||||
|
||||
function generateSummonGridImage(position: number) {
|
||||
let url = ''
|
||||
|
||||
const gridSummon = summonGrid[position]
|
||||
const summon = gridSummon?.object
|
||||
|
||||
const upgradedSummons = [
|
||||
'2040094000',
|
||||
'2040100000',
|
||||
'2040080000',
|
||||
'2040098000',
|
||||
'2040090000',
|
||||
'2040084000',
|
||||
'2040003000',
|
||||
'2040056000',
|
||||
'2040020000',
|
||||
'2040034000',
|
||||
'2040028000',
|
||||
'2040027000',
|
||||
'2040046000',
|
||||
'2040047000',
|
||||
]
|
||||
|
||||
if (summon && gridSummon) {
|
||||
// Change the image based on the uncap level
|
||||
let suffix = ''
|
||||
if (gridSummon.object.uncap.xlb && gridSummon.uncap_level == 6) {
|
||||
if (
|
||||
gridSummon.transcendence_step >= 1 &&
|
||||
gridSummon.transcendence_step < 5
|
||||
) {
|
||||
suffix = '_03'
|
||||
} else if (gridSummon.transcendence_step === 5) {
|
||||
suffix = '_04'
|
||||
}
|
||||
} else if (
|
||||
upgradedSummons.indexOf(summon.granblue_id.toString()) != -1 &&
|
||||
gridSummon.uncap_level == 5
|
||||
) {
|
||||
suffix = '_02'
|
||||
}
|
||||
|
||||
url = `${process.env.NEXT_PUBLIC_SIERO_IMG_URL}/summon-grid/${summon.granblue_id}${suffix}.jpg`
|
||||
}
|
||||
return (
|
||||
summons[position] && (
|
||||
<img alt={summons[position]?.name[locale]} src={url} />
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function sendSaveData() {
|
||||
if (props.onSave) props.onSave(props.id, props.favorited)
|
||||
if (onSave) onSave(party.id, party.favorited)
|
||||
}
|
||||
|
||||
const userImage = () => {
|
||||
if (props.user && props.user.avatar) {
|
||||
if (party.user && party.user.avatar) {
|
||||
return (
|
||||
<img
|
||||
alt={props.user.avatar.picture}
|
||||
className={`profile ${props.user.avatar.element}`}
|
||||
srcSet={`/profile/${props.user.avatar.picture}.png,
|
||||
/profile/${props.user.avatar.picture}@2x.png 2x`}
|
||||
src={`/profile/${props.user.avatar.picture}.png`}
|
||||
alt={party.user.avatar.picture}
|
||||
className={`profile ${party.user.avatar.element}`}
|
||||
srcSet={`/profile/${party.user.avatar.picture}.png,
|
||||
/profile/${party.user.avatar.picture}@2x.png 2x`}
|
||||
src={`/profile/${party.user.avatar.picture}.png`}
|
||||
/>
|
||||
)
|
||||
} else
|
||||
|
|
@ -194,29 +418,103 @@ const GridRep = (props: Props) => {
|
|||
const attribution = () => (
|
||||
<span className={userClass}>
|
||||
{userImage()}
|
||||
{props.user ? props.user.username : t('no_user')}
|
||||
{party.user ? party.user.username : t('no_user')}
|
||||
</span>
|
||||
)
|
||||
|
||||
function fullAutoString() {
|
||||
const fullAutoElement = (
|
||||
<span className={styles.fullAuto}>
|
||||
{` · ${t('party.details.labels.full_auto')}`}
|
||||
</span>
|
||||
)
|
||||
const renderWeaponGrid = (
|
||||
<div className={styles.weaponGrid}>
|
||||
<div className={mainhandClasses}>{generateMainhandImage()}</div>
|
||||
|
||||
const autoGuardElement = (
|
||||
<span className={styles.autoGuard}>
|
||||
<ShieldIcon />
|
||||
</span>
|
||||
)
|
||||
<ul className={styles.weapons}>
|
||||
{Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
<li
|
||||
key={`${party.shortcode}-weapon-${i}`}
|
||||
className={weaponClasses}
|
||||
>
|
||||
{generateWeaponGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={styles.auto}>
|
||||
{fullAutoElement}
|
||||
{props.autoGuard ? autoGuardElement : ''}
|
||||
const renderCharacterGrid = (
|
||||
<div className={styles.characterGrid}>
|
||||
<div className={protagonistClasses}>{generateMCImage()}</div>
|
||||
{Array.from(Array(3)).map((x, i) => {
|
||||
return (
|
||||
<li
|
||||
key={`${party.shortcode}-chara-${i}`}
|
||||
className={characterClasses}
|
||||
>
|
||||
{generateCharacterGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
|
||||
const renderSummonGrid = (
|
||||
<div className={styles.summonGrid}>
|
||||
<div className={styles.mainSummon}>{generateMainSummonImage('main')}</div>
|
||||
<ul className={styles.summons}>
|
||||
{Array.from(Array(numSummons)).map((x, i) => {
|
||||
return (
|
||||
<li key={`summons-${i}`} className={styles.summon}>
|
||||
{generateSummonGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<div className={styles.mainSummon}>
|
||||
{generateMainSummonImage('friend')}
|
||||
</div>
|
||||
)
|
||||
</div>
|
||||
)
|
||||
|
||||
const favoriteButton = (
|
||||
<Link href="#">
|
||||
<Button
|
||||
className={classNames({
|
||||
save: true,
|
||||
saved: party.favorited,
|
||||
})}
|
||||
leftAccessoryIcon={<SaveIcon className="stroke" />}
|
||||
active={party.favorited}
|
||||
bound={true}
|
||||
size="small"
|
||||
onClick={sendSaveData}
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
|
||||
function buttonArea() {
|
||||
if (
|
||||
account.authorized &&
|
||||
((party.user && account.user && account.user.id !== party.user.id) ||
|
||||
!party.user)
|
||||
) {
|
||||
return favoriteButton
|
||||
} else if (party.visibility === 2) {
|
||||
return (
|
||||
<Tooltip content={t('party.tooltips.unlisted')}>
|
||||
<span className={styles.icon}>
|
||||
<UnlistedIcon />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
} else if (party.visibility === 3) {
|
||||
return (
|
||||
<Tooltip content={t('party.tooltips.private')}>
|
||||
<span className={styles.icon}>
|
||||
<PrivateIcon />
|
||||
</span>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const detailsWithUsername = (
|
||||
|
|
@ -224,73 +522,84 @@ const GridRep = (props: Props) => {
|
|||
<div className={styles.top}>
|
||||
<div className={styles.info}>
|
||||
<h2 className={titleClass}>
|
||||
{props.name ? props.name : t('no_title')}
|
||||
{party.name ? party.name : t('no_title')}
|
||||
</h2>
|
||||
<div className={styles.properties}>
|
||||
<span className={raidClass}>
|
||||
{props.raid ? props.raid.name[locale] : t('no_raid')}
|
||||
{party.raid ? party.raid.name[locale] : t('no_raid')}
|
||||
</span>
|
||||
{props.fullAuto && (
|
||||
{party.full_auto && (
|
||||
<span className={styles.fullAuto}>
|
||||
{` · ${t('party.details.labels.full_auto')}`}
|
||||
</span>
|
||||
)}
|
||||
{props.raid && props.raid.group.extra && (
|
||||
{party.raid && party.raid.group.extra && (
|
||||
<span className={styles.extra}>{` · EX`}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{account.authorized &&
|
||||
((props.user && account.user && account.user.id !== props.user.id) ||
|
||||
!props.user) ? (
|
||||
<Link href="#">
|
||||
<Button
|
||||
className={classNames({
|
||||
save: true,
|
||||
saved: props.favorited,
|
||||
})}
|
||||
leftAccessoryIcon={<SaveIcon className="stroke" />}
|
||||
active={props.favorited}
|
||||
bound={true}
|
||||
size="small"
|
||||
onClick={sendSaveData}
|
||||
/>
|
||||
</Link>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{buttonArea()}
|
||||
</div>
|
||||
<div className={styles.attributed}>
|
||||
{attribution()}
|
||||
|
||||
<time
|
||||
className={styles.lastUpdated}
|
||||
dateTime={props.createdAt.toISOString()}
|
||||
dateTime={new Date(party.created_at).toISOString()}
|
||||
>
|
||||
{formatTimeAgo(props.createdAt, locale)}
|
||||
{formatTimeAgo(new Date(party.created_at), locale)}
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
return (
|
||||
<Link legacyBehavior href={`/p/${props.shortcode}`}>
|
||||
<a className={gridRepStyles}>
|
||||
{detailsWithUsername}
|
||||
<div className={styles.weaponGrid}>
|
||||
<div className={mainhandClasses}>{generateMainhandImage()}</div>
|
||||
function changeView(view: 'characters' | 'weapons' | 'summons') {
|
||||
setCurrentView(view)
|
||||
}
|
||||
|
||||
<ul className={styles.weapons}>
|
||||
{Array.from(Array(numWeapons)).map((x, i) => {
|
||||
return (
|
||||
<li key={`${props.shortcode}-${i}`} className={weaponClasses}>
|
||||
{generateGridImage(i)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
return (
|
||||
<Link
|
||||
href={`/p/${party.shortcode}`}
|
||||
className={gridRepClasses}
|
||||
onMouseLeave={() => changeView('weapons')}
|
||||
>
|
||||
{detailsWithUsername}
|
||||
<div className={styles.gridContainer}>
|
||||
{currentView === 'characters'
|
||||
? renderCharacterGrid
|
||||
: currentView === 'summons'
|
||||
? renderSummonGrid
|
||||
: renderWeaponGrid}
|
||||
</div>
|
||||
<ul className={styles.indicators}>
|
||||
<li
|
||||
className={classNames({
|
||||
[styles.active]: currentView === 'characters',
|
||||
})}
|
||||
onMouseEnter={() => changeView('characters')}
|
||||
>
|
||||
<div className={styles.indicator} />
|
||||
<span>Characters</span>
|
||||
</li>
|
||||
<li
|
||||
className={classNames({
|
||||
[styles.active]: currentView === 'weapons',
|
||||
})}
|
||||
onMouseEnter={() => changeView('weapons')}
|
||||
>
|
||||
<div className={styles.indicator} />
|
||||
<span>Weapons</span>
|
||||
</li>
|
||||
<li
|
||||
className={classNames({
|
||||
[styles.active]: currentView === 'summons',
|
||||
})}
|
||||
onMouseEnter={() => changeView('summons')}
|
||||
>
|
||||
<div className={styles.indicator} />
|
||||
<span>Summons</span>
|
||||
</li>
|
||||
</ul>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -444,7 +444,7 @@ const WeaponModal = ({
|
|||
<Alert
|
||||
message={
|
||||
<span>
|
||||
<Trans i18nKey="alerts.unsaved_changes.object">
|
||||
<Trans i18nKey="alert.unsaved_changes.object">
|
||||
You will lose all changes to{' '}
|
||||
<strong>{{ objectName: gridWeapon.object.name[locale] }}</strong> if
|
||||
you continue.
|
||||
|
|
@ -455,9 +455,9 @@ const WeaponModal = ({
|
|||
</span>
|
||||
}
|
||||
open={alertOpen}
|
||||
primaryActionText="Close"
|
||||
primaryActionText={t('alert.unsaved_changes.buttons.confirm')}
|
||||
primaryAction={close}
|
||||
cancelActionText="Nevermind"
|
||||
cancelActionText={t('alert.unsaved_changes.buttons.cancel')}
|
||||
cancelAction={() => setAlertOpen(false)}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -255,16 +255,7 @@ const ProfileRoute: React.FC<Props> = ({
|
|||
return parties.map((party, i) => {
|
||||
return (
|
||||
<GridRep
|
||||
id={party.id}
|
||||
shortcode={party.shortcode}
|
||||
name={party.name}
|
||||
createdAt={new Date(party.created_at)}
|
||||
raid={party.raid}
|
||||
grid={party.weapons}
|
||||
user={party.user}
|
||||
favorited={party.favorited}
|
||||
fullAuto={party.full_auto}
|
||||
autoGuard={party.auto_guard}
|
||||
party={party}
|
||||
key={`party-${i}`}
|
||||
loading={isLoading}
|
||||
onClick={goTo}
|
||||
|
|
|
|||
|
|
@ -294,16 +294,7 @@ const SavedRoute: React.FC<Props> = ({
|
|||
return parties.map((party, i) => {
|
||||
return (
|
||||
<GridRep
|
||||
id={party.id}
|
||||
shortcode={party.shortcode}
|
||||
name={party.name}
|
||||
createdAt={new Date(party.created_at)}
|
||||
raid={party.raid}
|
||||
grid={party.weapons}
|
||||
user={party.user}
|
||||
favorited={party.favorited}
|
||||
fullAuto={party.full_auto}
|
||||
autoGuard={party.auto_guard}
|
||||
party={party}
|
||||
key={`party-${i}`}
|
||||
loading={isLoading}
|
||||
onClick={goTo}
|
||||
|
|
|
|||
|
|
@ -308,16 +308,7 @@ const TeamsRoute: React.FC<Props> = ({
|
|||
return parties.map((party, i) => {
|
||||
return (
|
||||
<GridRep
|
||||
id={party.id}
|
||||
shortcode={party.shortcode}
|
||||
name={party.name}
|
||||
createdAt={new Date(party.created_at)}
|
||||
raid={party.raid}
|
||||
grid={party.weapons}
|
||||
user={party.user}
|
||||
favorited={party.favorited}
|
||||
fullAuto={party.full_auto}
|
||||
autoGuard={party.auto_guard}
|
||||
party={party}
|
||||
key={`party-${i}`}
|
||||
loading={isLoading}
|
||||
onClick={goTo}
|
||||
|
|
|
|||
3
public/icons/Private.svg
Normal file
3
public/icons/Private.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.625 4.27273C3.625 2.46525 5.13604 1 7 1C8.86396 1 10.375 2.46525 10.375 4.27273V6.45455H10.4056C11.01 6.45455 11.5 6.94454 11.5 7.54897V11.9056C11.5 12.51 11.01 13 10.4056 13H3.59443C2.98999 13 2.5 12.51 2.5 11.9056V7.54897C2.5 6.94454 2.98999 6.45455 3.59443 6.45455H3.625V4.27273ZM5.3125 6.45455H8.6875V4.27273C8.6875 3.4678 8.30444 2.63636 7 2.63636C5.69557 2.63636 5.3125 3.45827 5.3125 4.27273V6.45455Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 532 B |
4
public/icons/Public.svg
Normal file
4
public/icons/Public.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.04515 9.5C8.42586 9.5 9.54515 8.38071 9.54515 7C9.54515 5.61929 8.42586 4.5 7.04515 4.5C5.66444 4.5 4.54515 5.61929 4.54515 7C4.54515 8.38071 5.66444 9.5 7.04515 9.5ZM7.04515 8.5C7.87358 8.5 8.54515 7.82843 8.54515 7C8.54515 6.17157 7.87358 5.5 7.04515 5.5C6.21672 5.5 5.54515 6.17157 5.54515 7C5.54515 7.82843 6.21672 8.5 7.04515 8.5Z" />
|
||||
<path d="M13.2118 6.44776C10.1648 1.67656 4.08822 1.61674 0.814821 6.43618C0.611758 6.73515 0.607725 7.12607 0.803991 7.42954C3.9948 12.3633 10.0268 12.435 13.2119 7.41171C13.3986 7.11736 13.3994 6.7415 13.2118 6.44776Z" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 719 B |
3
public/icons/Unlisted.svg
Normal file
3
public/icons/Unlisted.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg viewBox="0 0 14 14" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.9358 6.38178C14.0712 6.14109 13.9858 5.83624 13.7451 5.70088C13.5044 5.56552 13.1996 5.6509 13.0642 5.89159C11.5427 8.59686 9.24574 9.87242 7.00066 9.86173C4.753 9.85102 2.45409 8.5506 0.934206 5.88876C0.79728 5.64896 0.49188 5.56556 0.252076 5.70248C0.012272 5.83941 -0.0711275 6.14481 0.0657981 6.38461C0.666775 7.43713 1.38868 8.31027 2.19043 8.99743C2.16108 9.0253 2.13467 9.0572 2.11204 9.09296L1.57636 9.93929C1.42867 10.1726 1.4981 10.4815 1.73143 10.6292C1.96476 10.7769 2.27364 10.7074 2.42132 10.4741L2.957 9.62777C2.96384 9.61698 2.9702 9.60602 2.97611 9.59493C3.33838 9.83773 3.71284 10.0462 4.09629 10.2199C4.08317 10.2433 4.07177 10.268 4.0623 10.2941L3.71972 11.2353C3.62528 11.4948 3.75907 11.7817 4.01856 11.8761C4.27805 11.9706 4.56497 11.8368 4.65942 11.5773L5.00199 10.6361C5.01037 10.613 5.01695 10.5898 5.02181 10.5665C5.50693 10.7115 6.00176 10.8035 6.5004 10.8416C6.50014 10.8483 6.5 10.855 6.5 10.8618V11.8634C6.5 12.1395 6.72386 12.3634 7 12.3634C7.27614 12.3634 7.5 12.1395 7.5 11.8634V10.8618L7.49975 10.8458C7.99944 10.8117 8.49534 10.7233 8.9815 10.5811C8.98592 10.5995 8.99142 10.6179 8.99805 10.6361L9.34063 11.5773C9.43507 11.8368 9.72199 11.9706 9.98148 11.8761C10.241 11.7817 10.3748 11.4948 10.2803 11.2353L9.93775 10.2941C9.93055 10.2743 9.92222 10.2552 9.91288 10.2369C10.2967 10.0644 10.6715 9.85639 11.0341 9.6131L11.043 9.62777L11.5787 10.4741C11.7264 10.7074 12.0353 10.7769 12.2686 10.6292C12.5019 10.4815 12.5714 10.1726 12.4237 9.93929L11.888 9.09296C11.8689 9.06272 11.847 9.03523 11.823 9.01061C12.6198 8.32285 13.3376 7.44546 13.9358 6.38178Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -12,11 +12,15 @@
|
|||
},
|
||||
"alert": {
|
||||
"confirm_logout": "Are you sure you want to log out?",
|
||||
"incompatible_weapon": "You've selected a weapon that can't be added to the Additional Weapon slots.",
|
||||
"unsaved_changes": {
|
||||
"party": "You will lose all changes to your party <strong>{objectName}</strong> if you continue.<br/><br/>Are you sure you want to continue without saving?",
|
||||
"object": "You will lose all changes to <strong>{objectName}</strong> if you continue.<br/><br/>Are you sure you want to continue without saving?"
|
||||
},
|
||||
"incompatible_weapon": "You've selected a weapon that can't be added to the Additional Weapon slots."
|
||||
"party": "You will lose all changes to your party <strong>{{objectName}}</strong> if you continue.<br/><br/>Are you sure you want to continue without saving?",
|
||||
"object": "You will lose all changes to <strong>{{objectName}}</strong> if you continue.<br/><br/>Are you sure you want to continue without saving?",
|
||||
"buttons": {
|
||||
"confirm": "Continue without saving",
|
||||
"cancel": "Nevermind"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ax": {
|
||||
"no_skill": "No AX Skill",
|
||||
|
|
@ -65,6 +69,7 @@
|
|||
},
|
||||
"dropdown": {
|
||||
"party": {
|
||||
"visibility": "Change team visibility",
|
||||
"copy": "Copy link to team",
|
||||
"delete": "Delete team",
|
||||
"remix": "Remix team"
|
||||
|
|
@ -397,6 +402,33 @@
|
|||
"remove": "Remove summon"
|
||||
}
|
||||
},
|
||||
"team_visibility": {
|
||||
"title": "Change team visibility",
|
||||
"label": "Team visibility",
|
||||
"description": "Change who can see this team and where it shows up on the site",
|
||||
"alerts": {
|
||||
"unsaved_changes": {
|
||||
"message": "Are you sure you want to continue without changing your team's visibility?",
|
||||
"buttons": {
|
||||
"confirm": "Continue without saving",
|
||||
"cancel": "Nevermind"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"public": "Public",
|
||||
"unlisted": "Unlisted",
|
||||
"private": "Private"
|
||||
},
|
||||
"descriptions": {
|
||||
"public": "Visible to everyone and appears on the Teams page",
|
||||
"unlisted": "Only visible to people with the link and does not appear on the Teams page",
|
||||
"private": "Only visible to you and does not appear on the Teams page"
|
||||
},
|
||||
"buttons": {
|
||||
"confirm": "Change visibility"
|
||||
}
|
||||
},
|
||||
"weapon": {
|
||||
"title": "Modify Weapon",
|
||||
"buttons": {
|
||||
|
|
@ -459,6 +491,18 @@
|
|||
"with_count_one": "{{count}} turn",
|
||||
"with_count_other": "{{count}} turns"
|
||||
}
|
||||
},
|
||||
"notices": {
|
||||
"unlisted": "This party is unlisted. Only people with the link with can see it.",
|
||||
"private": "This party is private. Only you can see it.",
|
||||
"buttons": {
|
||||
"copy_link": "Copy link",
|
||||
"change_visibility": "Change visibility"
|
||||
}
|
||||
},
|
||||
"tooltips": {
|
||||
"unlisted": "This party is unlisted",
|
||||
"private": "This party is private"
|
||||
}
|
||||
},
|
||||
"proficiencies": {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,15 @@
|
|||
},
|
||||
"alert": {
|
||||
"confirm_logout": "ログアウトしますか",
|
||||
"unsaved_changes_party": "<strong>「{objectName}」</strong>という編成の変更は保存していません。<br/><br/>変更を保存せずに続けますか?",
|
||||
"unsaved_changes_object": "<strong>「{objectName}」</strong>の変更は保存していません。<br/><br/>変更を保存せずに続けますか?",
|
||||
"incompatible_weapon": "Additional Weaponsに装備できない武器を入れました。"
|
||||
"incompatible_weapon": "Additional Weaponsに装備できない武器を入れました。",
|
||||
"unsaved_changes": {
|
||||
"party": "<strong>「{{objectName}}」</strong>という編成の変更は保存していません。<br/><br/>変更を保存せずに続けますか?",
|
||||
"object": "<strong>「{{objectName}}」</strong>の変更は保存していません。<br/><br/>変更を保存せずに続けますか?",
|
||||
"buttons": {
|
||||
"confirm": "変更せずに続ける",
|
||||
"cancel": "キャンセル"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ax": {
|
||||
"no_skill": "EXスキルなし",
|
||||
|
|
@ -63,6 +69,7 @@
|
|||
},
|
||||
"dropdown": {
|
||||
"party": {
|
||||
"visibility": "編成のプライバシー設定を変更",
|
||||
"copy": "編成のリンクをコピー",
|
||||
"delete": "編成を削除",
|
||||
"remix": "編成をリミックス"
|
||||
|
|
@ -395,6 +402,33 @@
|
|||
"remove": "召喚石を削除する"
|
||||
}
|
||||
},
|
||||
"team_visibility": {
|
||||
"title": "編成のプライバシー設定を変更",
|
||||
"label": "プライバシー設定",
|
||||
"description": "この編成は誰が共有できるかと編成一覧に表示されるかを変更できます",
|
||||
"alerts": {
|
||||
"unsaved_changes": {
|
||||
"message": "編成のプライバシー設定を変更せずに続けますか?",
|
||||
"buttons": {
|
||||
"confirm": "変更せずに続ける",
|
||||
"cancel": "キャンセル"
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"public": "公開",
|
||||
"unlisted": "限定公開",
|
||||
"private": "非公開"
|
||||
},
|
||||
"descriptions": {
|
||||
"public": "誰でも自由に共有可能。編成一覧に表示します。",
|
||||
"unlisted": "リンクが知っている人のみが共有可能。編成一覧に表示しません。",
|
||||
"private": "自分自身だけが可視。編成一覧に表示しません。"
|
||||
},
|
||||
"buttons": {
|
||||
"confirm": "プライバシー設定を変更"
|
||||
}
|
||||
},
|
||||
"weapon": {
|
||||
"title": "武器変更",
|
||||
"buttons": {
|
||||
|
|
@ -453,10 +487,23 @@
|
|||
"minutes": "分",
|
||||
"seconds": "秒"
|
||||
},
|
||||
|
||||
"turns": {
|
||||
"with_count_one": "{{count}}ターン",
|
||||
"with_count_other": "{{count}}ターン"
|
||||
}
|
||||
},
|
||||
"notices": {
|
||||
"unlisted": "この編成は限定公開でリンクが知っている人のみが共有可能",
|
||||
"private": "この編成は未公開で自分自身だけが可視",
|
||||
"buttons": {
|
||||
"copy_link": "リンクをコピー",
|
||||
"change_visibility": "プライバシー設定を変更"
|
||||
}
|
||||
},
|
||||
"tooltips": {
|
||||
"unlisted": "この編成は限定公開",
|
||||
"private": "この編成は未公開"
|
||||
}
|
||||
},
|
||||
"proficiencies": {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@
|
|||
--notice-bg: #{$notice--bg--light};
|
||||
--notice-text: #{$notice--text--light};
|
||||
|
||||
--notice-button-bg: #{$notice--button--bg--light};
|
||||
--notice-button-bg-hover: #{$notice--button--bg--light--hover};
|
||||
--notice-button-text: #{$notice--button--text--light};
|
||||
--notice-button-text-hover: #{$notice--button--text--light--hover};
|
||||
|
||||
// Light - Buttons
|
||||
--button-bg: #{$button--bg--light};
|
||||
--button-bg-hover: #{$button--bg--light--hover};
|
||||
|
|
@ -112,6 +117,13 @@
|
|||
--slider-thumb-shadow: #{$slider--thumb--shadow--light};
|
||||
--slider-thumb-shadow-hover: #{$slider--thumb--shadow--light--hover};
|
||||
|
||||
// Light - Radio Buttons
|
||||
--radio-button-bg: #{$radio--bg--light};
|
||||
--radio-button-bg-hover: #{$radio--bg--light--hover};
|
||||
|
||||
--radio-active-bg: #{$radio--active--bg--light};
|
||||
--radio-active-bg-hover: #{$radio--active--bg--light--hover};
|
||||
|
||||
// Light - About
|
||||
--link-item-bg: #{$link--item--bg--light};
|
||||
--link-item-image-color: #{$link--item--bg--image--light};
|
||||
|
|
@ -173,6 +185,7 @@
|
|||
|
||||
--wind-bg: #{$wind--bg--light};
|
||||
--wind-bg-hover: #{$wind--bg--hover--light};
|
||||
--wind-portrait-bg: #{$wind--portrait--bg--light};
|
||||
--wind-text: #{$wind--text--light};
|
||||
--wind-raid-text: #{$wind--text--raid--light};
|
||||
--wind-text-hover: #{$wind--text--hover--light};
|
||||
|
|
@ -181,6 +194,7 @@
|
|||
|
||||
--fire-bg: #{$fire--bg--light};
|
||||
--fire-bg-hover: #{$fire--bg--hover--light};
|
||||
--fire-portrait-bg: #{$fire--portrait--bg--light};
|
||||
--fire-text: #{$fire--text--light};
|
||||
--fire-raid-text: #{$fire--text--raid--light};
|
||||
--fire-text-hover: #{$fire--text--hover--light};
|
||||
|
|
@ -189,6 +203,7 @@
|
|||
|
||||
--water-bg: #{$water--bg--light};
|
||||
--water-bg-hover: #{$water--bg--hover--light};
|
||||
--water-portrait-bg: #{$water--portrait--bg--light};
|
||||
--water-text: #{$water--text--light};
|
||||
--water-raid-text: #{$water--text--raid--light};
|
||||
--water-text-hover: #{$water--text--hover--light};
|
||||
|
|
@ -197,6 +212,7 @@
|
|||
|
||||
--earth-bg: #{$earth--bg--light};
|
||||
--earth-bg-hover: #{$earth--bg--hover--light};
|
||||
--earth-portrait-bg: #{$earth--portrait--bg--light};
|
||||
--earth-text: #{$earth--text--light};
|
||||
--earth-raid-text: #{$earth--text--raid--light};
|
||||
--earth-text-hover: #{$earth--text--hover--light};
|
||||
|
|
@ -205,6 +221,7 @@
|
|||
|
||||
--dark-bg: #{$dark--bg--light};
|
||||
--dark-bg-hover: #{$dark--bg--hover--light};
|
||||
--dark-portrait-bg: #{$dark--portrait--bg--light};
|
||||
--dark-text: #{$dark--text--light};
|
||||
--dark-raid-text: #{$dark--text--raid--light};
|
||||
--dark-text-hover: #{$dark--text--hover--light};
|
||||
|
|
@ -213,6 +230,7 @@
|
|||
|
||||
--light-bg: #{$light--bg--light};
|
||||
--light-bg-hover: #{$light--bg--hover--light};
|
||||
--light-portrait-bg: #{$light--portrait--bg--light};
|
||||
--light-text: #{$light--text--light};
|
||||
--light-raid-text: #{$light--text--raid--light};
|
||||
--light-text-hover: #{$light--text--hover--light};
|
||||
|
|
@ -307,6 +325,11 @@
|
|||
--notice-bg: #{$notice--bg--dark};
|
||||
--notice-text: #{$notice--text--dark};
|
||||
|
||||
--notice-button-bg: #{$notice--button--bg--dark};
|
||||
--notice-button-bg-hover: #{$notice--button--bg--dark--hover};
|
||||
--notice-button-text: #{$notice--button--text--dark};
|
||||
--notice-button-text-hover: #{$notice--button--text--dark--hover};
|
||||
|
||||
// Dark - Buttons
|
||||
--button-bg: #{$button--bg--dark};
|
||||
--button-bg-hover: #{$button--bg--dark--hover};
|
||||
|
|
@ -338,6 +361,13 @@
|
|||
--slider-thumb-shadow: #{$slider--thumb--shadow--dark};
|
||||
--slider-thumb-shadow-hover: #{$slider--thumb--shadow--dark--hover};
|
||||
|
||||
// Dark - Radio Buttons
|
||||
--radio-button-bg: #{$radio--bg--dark};
|
||||
--radio-button-bg-hover: #{$radio--bg--dark--hover};
|
||||
|
||||
--radio-active-bg: #{$radio--active--bg--dark};
|
||||
--radio-active-bg-hover: #{$radio--active--bg--dark--hover};
|
||||
|
||||
// Dark - About
|
||||
--link-item-bg: #{$link--item--bg--dark};
|
||||
--link-item-image-color: #{$link--item--bg--image--dark};
|
||||
|
|
@ -399,6 +429,7 @@
|
|||
|
||||
--wind-bg: #{$wind--bg--dark};
|
||||
--wind-bg-hover: #{$wind--bg--hover--dark};
|
||||
--wind-portrait-bg: #{$wind--portrait--bg--dark};
|
||||
--wind-text: #{$wind--text--dark};
|
||||
--wind-raid-text: #{$wind--text--raid--dark};
|
||||
--wind-text-hover: #{$wind--text--hover--dark};
|
||||
|
|
@ -407,6 +438,7 @@
|
|||
|
||||
--fire-bg: #{$fire--bg--dark};
|
||||
--fire-bg-hover: #{$fire--bg--hover--dark};
|
||||
--fire-portrait-bg: #{$fire--portrait--bg--dark};
|
||||
--fire-text: #{$fire--text--dark};
|
||||
--fire-raid-text: #{$fire--text--raid--dark};
|
||||
--fire-text-hover: #{$fire--text--hover--dark};
|
||||
|
|
@ -415,6 +447,7 @@
|
|||
|
||||
--water-bg: #{$water--bg--dark};
|
||||
--water-bg-hover: #{$water--bg--hover--dark};
|
||||
--water-portrait-bg: #{$water--portrait--bg--dark};
|
||||
--water-text: #{$water--text--dark};
|
||||
--water-raid-text: #{$water--text--raid--dark};
|
||||
--water-text-hover: #{$water--text--hover--dark};
|
||||
|
|
@ -423,6 +456,7 @@
|
|||
|
||||
--earth-bg: #{$earth--bg--dark};
|
||||
--earth-bg-hover: #{$earth--bg--hover--dark};
|
||||
--earth-portrait-bg: #{$earth--portrait--bg--dark};
|
||||
--earth-text: #{$earth--text--dark};
|
||||
--earth-raid-text: #{$earth--text--raid--dark};
|
||||
--earth-text-hover: #{$earth--text--hover--dark};
|
||||
|
|
@ -431,6 +465,7 @@
|
|||
|
||||
--dark-bg: #{$dark--bg--dark};
|
||||
--dark-bg-hover: #{$dark--bg--hover--dark};
|
||||
--dark-portrait-bg: #{$dark--portrait--bg--dark};
|
||||
--dark-text: #{$dark--text--dark};
|
||||
--dark-raid-text: #{$dark--text--raid--dark};
|
||||
--dark-text-hover: #{$dark--text--hover--dark};
|
||||
|
|
@ -439,6 +474,7 @@
|
|||
|
||||
--light-bg: #{$light--bg--dark};
|
||||
--light-bg-hover: #{$light--bg--hover--dark};
|
||||
--light-portrait-bg: #{$light--portrait--bg--dark};
|
||||
--light-text: #{$light--text--dark};
|
||||
--light-raid-text: #{$light--text--raid--dark};
|
||||
--light-text-hover: #{$light--text--hover--dark};
|
||||
|
|
|
|||
|
|
@ -107,10 +107,13 @@ $yellow-text-20: #ffed4c;
|
|||
$highlight-yellow: #ffed4c55;
|
||||
|
||||
$accent--yellow--00: #463805;
|
||||
$accent--yellow--20: #7f6a00;
|
||||
$accent--yellow--10: #6c5a01;
|
||||
$accent--yellow--20: #776300;
|
||||
$accent--yellow--40: #a39200;
|
||||
$accent--yellow--60: #c89d39;
|
||||
$accent--yellow--70: #d1aa4f;
|
||||
$accent--yellow--80: #deb351;
|
||||
$accent--yellow--90: #e6bd5e;
|
||||
$accent--yellow--100: #f9cc64;
|
||||
|
||||
$selected--item--bg--dark: #f9cc645d;
|
||||
|
|
@ -222,6 +225,18 @@ $notice--bg--dark: $accent--yellow--00;
|
|||
$notice--text--light: $accent--yellow--20;
|
||||
$notice--text--dark: $accent--yellow--100;
|
||||
|
||||
$notice--button--bg--light: $accent--yellow--80;
|
||||
$notice--button--bg--dark: $accent--yellow--20;
|
||||
|
||||
$notice--button--bg--light--hover: $accent--yellow--70;
|
||||
$notice--button--bg--dark--hover: $accent--yellow--10;
|
||||
|
||||
$notice--button--text--light: $accent--yellow--10;
|
||||
$notice--button--text--dark: $accent--yellow--90;
|
||||
|
||||
$notice--button--text--light--hover: $accent--yellow--00;
|
||||
$notice--button--text--dark--hover: $accent--yellow--100;
|
||||
|
||||
// Color Definitions: Button
|
||||
$button--bg--light: $grey-80;
|
||||
$button--bg--light--hover: $grey-100;
|
||||
|
|
@ -440,6 +455,19 @@ $icon--secondary--color--dark: $grey-50;
|
|||
$icon--secondary--hover--light: $grey-50;
|
||||
$icon--secondary--hover--dark: $grey-70;
|
||||
|
||||
// Color Definitions: Radio Buttons
|
||||
$radio--bg--light: $grey-75;
|
||||
$radio--bg--dark: $grey-10;
|
||||
|
||||
$radio--bg--light--hover: $grey-70;
|
||||
$radio--bg--dark--hover: $grey-00;
|
||||
|
||||
$radio--active--bg--light: $accent--blue--light;
|
||||
$radio--active--bg--dark: $accent--blue--dark;
|
||||
|
||||
$radio--active--bg--light--hover: $accent--blue--light--focus;
|
||||
$radio--active--bg--dark--hover: $accent--blue--dark--focus;
|
||||
|
||||
// Color Definitions: Tag
|
||||
$tag--bg--light: $grey-60;
|
||||
$tag--bg--dark: $grey-00;
|
||||
|
|
@ -450,6 +478,9 @@ $tag--text--dark: $grey-50;
|
|||
$wind--bg--light: $wind-bg-10;
|
||||
$wind--bg--dark: $wind-bg-10;
|
||||
|
||||
$wind--portrait--bg--light: $wind-bg-20;
|
||||
$wind--portrait--bg--dark: $wind-bg-20;
|
||||
|
||||
$wind--bg--hover--light: $wind-bg-00;
|
||||
$wind--bg--hover--dark: $wind-bg-00;
|
||||
|
||||
|
|
@ -494,6 +525,9 @@ $null--shadow--dark--hover: fade-out($grey-10, 0.3);
|
|||
$fire--bg--light: $fire-bg-10;
|
||||
$fire--bg--dark: $fire-bg-10;
|
||||
|
||||
$fire--portrait--bg--light: $fire-bg-20;
|
||||
$fire--portrait--bg--dark: $fire-bg-20;
|
||||
|
||||
$fire--bg--hover--light: $fire-bg-00;
|
||||
$fire--bg--hover--dark: $fire-bg-00;
|
||||
|
||||
|
|
@ -516,6 +550,9 @@ $fire--shadow--dark--hover: fade-out($fire-text-00, 0.3);
|
|||
$water--bg--light: $water-bg-10;
|
||||
$water--bg--dark: $water-bg-10;
|
||||
|
||||
$water--portrait--bg--light: $water-bg-20;
|
||||
$water--portrait--bg--dark: $water-bg-20;
|
||||
|
||||
$water--bg--hover--light: $water-bg-00;
|
||||
$water--bg--hover--dark: $water-bg-00;
|
||||
|
||||
|
|
@ -538,6 +575,9 @@ $water--shadow--dark--hover: fade-out($water-text-00, 0.3);
|
|||
$earth--bg--light: $earth-bg-10;
|
||||
$earth--bg--dark: $earth-bg-10;
|
||||
|
||||
$earth--portrait--bg--light: $earth-bg-20;
|
||||
$earth--portrait--bg--dark: $earth-bg-20;
|
||||
|
||||
$earth--bg--hover--light: $earth-bg-00;
|
||||
$earth--bg--hover--dark: $earth-bg-00;
|
||||
|
||||
|
|
@ -560,6 +600,9 @@ $earth--shadow--dark--hover: fade-out($earth-text-00, 0.3);
|
|||
$dark--bg--light: $dark-bg-10;
|
||||
$dark--bg--dark: $dark-bg-10;
|
||||
|
||||
$dark--portrait--bg--light: $dark-bg-20;
|
||||
$dark--portrait--bg--dark: $dark-bg-20;
|
||||
|
||||
$dark--bg--hover--light: $dark-bg-00;
|
||||
$dark--bg--hover--dark: $dark-bg-00;
|
||||
|
||||
|
|
@ -582,6 +625,9 @@ $dark--shadow--dark--hover: fade-out($dark-text-00, 0.3);
|
|||
$light--bg--light: $light-bg-10;
|
||||
$light--bg--dark: $light-bg-10;
|
||||
|
||||
$light--portrait--bg--light: $light-bg-20;
|
||||
$light--portrait--bg--dark: $light-bg-20;
|
||||
|
||||
$light--bg--hover--light: $light-bg-00;
|
||||
$light--bg--hover--dark: $light-bg-00;
|
||||
|
||||
|
|
|
|||
1
types/Party.d.ts
vendored
1
types/Party.d.ts
vendored
|
|
@ -43,6 +43,7 @@ interface Party {
|
|||
local_id?: string
|
||||
remix: boolean
|
||||
remixes: Party[]
|
||||
visibility: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
|
|
|||
1
types/index.d.ts
vendored
1
types/index.d.ts
vendored
|
|
@ -41,6 +41,7 @@ export type DetailsObject = {
|
|||
job?: Job
|
||||
extra?: boolean
|
||||
guidebooks?: string[]
|
||||
visibility?: number
|
||||
}
|
||||
|
||||
export type ExtendedMastery = {
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ interface AppState {
|
|||
favorited: boolean
|
||||
remix: boolean
|
||||
remixes: Party[]
|
||||
visibility: number
|
||||
sourceParty?: Party
|
||||
created_at: string
|
||||
updated_at: string
|
||||
|
|
@ -128,6 +129,7 @@ export const initialAppState: AppState = {
|
|||
favorited: false,
|
||||
remix: false,
|
||||
remixes: [],
|
||||
visibility: 1,
|
||||
sourceParty: undefined,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
|
|
|
|||
Loading…
Reference in a new issue