Merge pull request #175 from jedmund/remix

Implement remixes
This commit is contained in:
Justin Edmund 2023-01-28 03:51:31 -08:00 committed by GitHub
commit f293f92e23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 401 additions and 52 deletions

View file

@ -31,6 +31,15 @@
background: transparent;
}
&.IconButton.medium {
height: inherit;
padding: $unit-half;
&:hover {
background: none;
}
}
&.Contained {
background: var(--button-contained-bg);

View file

@ -9,7 +9,7 @@ import Link from 'next/link'
import api from '~utils/api'
import { accountState, initialAccountState } from '~utils/accountState'
import { appState, initialAppState } from '~utils/appState'
import { appState } from '~utils/appState'
import capitalizeFirstLetter from '~utils/capitalizeFirstLetter'
import {
@ -26,12 +26,14 @@ import AccountModal from '~components/AccountModal'
import Toast from '~components/Toast'
import Button from '~components/Button'
import ArrowIcon from '~public/icons/Arrow.svg'
import LinkIcon from '~public/icons/Link.svg'
import MenuIcon from '~public/icons/Menu.svg'
import ArrowIcon from '~public/icons/Arrow.svg'
import RemixIcon from '~public/icons/Remix.svg'
import SaveIcon from '~public/icons/Save.svg'
import './index.scss'
import Tooltip from '~components/Tooltip'
const Header = () => {
// Localization
@ -83,10 +85,6 @@ const Header = () => {
setRightMenuOpen(false)
}
function handleSettingsOpenChanged(open: boolean) {
setRightMenuOpen(false)
}
function copyToClipboard() {
const el = document.createElement('input')
el.value = window.location.href
@ -106,15 +104,6 @@ const Header = () => {
// Push the root URL
router.push(path)
// Clean state
const resetState = clonedeep(initialAppState)
Object.keys(resetState).forEach((key) => {
appState[key] = resetState[key]
})
// Set party to be editable
appState.party.editable = true
// Close right menu
closeRightMenu()
}
@ -158,6 +147,14 @@ const Header = () => {
else console.error('Failed to unsave team: No party ID')
}
function remixTeam() {
if (party.shortcode)
api.remix(party.shortcode).then((response) => {
const remix = response.data.party
router.push(`/p/${remix.shortcode}`)
})
}
const pageTitle = () => {
let title = ''
let hasAccessory = false
@ -219,7 +216,7 @@ const Header = () => {
open={copyToastOpen}
duration={2400}
type="foreground"
content="This party's URL was copied to your clipboard"
content={t('toasts.copied')}
onOpenChange={handleCopyToastOpenChanged}
onCloseClick={handleCopyToastCloseClicked}
/>
@ -228,16 +225,32 @@ const Header = () => {
const saveButton = () => {
return (
<Button
leftAccessoryIcon={<SaveIcon />}
className={classNames({
Save: true,
Saved: party.favorited,
})}
blended={true}
text={party.favorited ? 'Saved' : 'Save'}
onClick={toggleFavorite}
/>
<Tooltip content={t('tooltips.save')}>
<Button
leftAccessoryIcon={<SaveIcon />}
className={classNames({
Save: true,
Saved: party.favorited,
})}
blended={true}
text={party.favorited ? t('buttons.saved') : t('buttons.save')}
onClick={toggleFavorite}
/>
</Tooltip>
)
}
const remixButton = () => {
return (
<Tooltip content={t('tooltips.remix')}>
<Button
leftAccessoryIcon={<RemixIcon />}
className="Remix"
blended={true}
text={t('buttons.remix')}
onClick={remixTeam}
/>
</Tooltip>
)
}
@ -303,7 +316,7 @@ const Header = () => {
(!party.user || party.user.id !== account.user.id)
? saveButton()
: ''}
{router.route === '/p/[party]' ? remixButton() : ''}
<DropdownMenu
open={rightMenuOpen}
onOpenChange={handleRightMenuOpenChange}

View file

@ -150,9 +150,13 @@ const Party = (props: Props) => {
appState.party.accessory = team.accessory
appState.party.id = team.id
appState.party.shortcode = team.shortcode
appState.party.extra = team.extra
appState.party.user = team.user
appState.party.favorited = team.favorited
appState.party.remix = team.remix
appState.party.remixes = team.remixes
appState.party.sourceParty = team.source_party
appState.party.created_at = team.created_at
appState.party.updated_at = team.updated_at

View file

@ -3,6 +3,7 @@
flex-direction: column;
margin: $unit-4x auto 0 auto;
max-width: $grid-width;
padding-bottom: $unit-12x;
@include breakpoint(phone) {
padding: 0 $unit;
@ -10,13 +11,13 @@
.PartyDetails {
display: none;
margin: 0 auto;
margin: 0 auto $unit-2x;
max-width: $unit * 94;
overflow: hidden;
width: 100%;
&.Visible {
margin-bottom: $unit-12x;
// margin-bottom: $unit-12x;
}
&.Editable {
@ -269,15 +270,21 @@
.Left {
flex-grow: 1;
h1 {
font-size: $font-xlarge;
font-weight: $normal;
text-align: left;
.Header {
align-items: center;
display: flex;
gap: $unit;
margin-bottom: $unit;
color: var(--text-primary);
&.empty {
color: var(--text-secondary);
h1 {
font-size: $font-xlarge;
font-weight: $normal;
text-align: left;
color: var(--text-primary);
&.empty {
color: var(--text-secondary);
}
}
}
@ -332,4 +339,26 @@
}
}
}
.Remixes {
display: flex;
flex-direction: column;
gap: $unit-2x;
width: 752px;
h3 {
font-size: $font-medium;
font-weight: $medium;
}
.GridRepCollection {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
margin-left: $unit-2x * -1;
margin-right: $unit-2x * -1;
.GridRep {
min-width: 200px;
}
}
}
}

View file

@ -3,6 +3,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useSnapshot } from 'valtio'
import { useTranslation } from 'next-i18next'
import clonedeep from 'lodash.clonedeep'
import Linkify from 'react-linkify'
import LiteYouTubeEmbed from 'react-lite-youtube-embed'
@ -10,17 +11,19 @@ import classNames from 'classnames'
import reactStringReplace from 'react-string-replace'
import Alert from '~components/Alert'
import Button from '~components/Button'
import CharLimitedFieldset from '~components/CharLimitedFieldset'
import Input from '~components/Input'
import DurationInput from '~components/DurationInput'
import GridRepCollection from '~components/GridRepCollection'
import GridRep from '~components/GridRep'
import Input from '~components/Input'
import RaidDropdown from '~components/RaidDropdown'
import Switch from '~components/Switch'
import Tooltip from '~components/Tooltip'
import TextFieldset from '~components/TextFieldset'
import Token from '~components/Token'
import RaidDropdown from '~components/RaidDropdown'
import TextFieldset from '~components/TextFieldset'
import Switch from '~components/Switch'
import api from '~utils/api'
import { accountState } from '~utils/accountState'
import { appState } from '~utils/appState'
import { formatTimeAgo } from '~utils/timeAgo'
@ -29,6 +32,7 @@ import { youtube } from '~utils/youtube'
import CheckIcon from '~public/icons/Check.svg'
import CrossIcon from '~public/icons/Cross.svg'
import EditIcon from '~public/icons/Edit.svg'
import RemixIcon from '~public/icons/Remix.svg'
import type { DetailsObject } from 'types'
@ -69,6 +73,8 @@ const PartyDetails = (props: Props) => {
const [turnCount, setTurnCount] = useState<number | undefined>(undefined)
const [clearTime, setClearTime] = useState(0)
const [remixes, setRemixes] = useState<Party[]>([])
const [raidSlug, setRaidSlug] = useState('')
const [embeddedDescription, setEmbeddedDescription] =
useState<React.ReactNode>()
@ -111,6 +117,7 @@ const PartyDetails = (props: Props) => {
setFullAuto(props.party.full_auto)
setChargeAttack(props.party.charge_attack)
setClearTime(props.party.clear_time)
setRemixes(props.party.remixes)
if (props.party.turn_count) setTurnCount(props.party.turn_count)
if (props.party.button_count) setButtonCount(props.party.button_count)
if (props.party.chain_count) setChainCount(props.party.chain_count)
@ -300,6 +307,49 @@ const PartyDetails = (props: Props) => {
props.deleteCallback()
}
// Methods: Navigation
function goTo(shortcode?: string) {
if (shortcode) router.push(`/p/${shortcode}`)
}
// Methods: Favorites
function toggleFavorite(teamId: string, favorited: boolean) {
if (favorited) unsaveFavorite(teamId)
else saveFavorite(teamId)
}
function saveFavorite(teamId: string) {
api.saveTeam({ id: teamId }).then((response) => {
if (response.status == 201) {
const index = remixes.findIndex((p) => p.id === teamId)
const party = remixes[index]
party.favorited = true
let clonedParties = clonedeep(remixes)
clonedParties[index] = party
setRemixes(clonedParties)
}
})
}
function unsaveFavorite(teamId: string) {
api.unsaveTeam({ id: teamId }).then((response) => {
if (response.status == 200) {
const index = remixes.findIndex((p) => p.id === teamId)
const party = remixes[index]
party.favorited = false
let clonedParties = clonedeep(remixes)
clonedParties[index] = party
setRemixes(clonedParties)
}
})
}
function extractYoutubeVideoIds(text: string) {
// Initialize an array to store the video IDs
const videoIds = []
@ -388,6 +438,28 @@ const PartyDetails = (props: Props) => {
)
}
function renderRemixes() {
return 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}
key={`party-${i}`}
displayUser={true}
onClick={goTo}
onSave={toggleFavorite}
/>
)
})
}
const deleteAlert = () => {
if (party.editable) {
return (
@ -620,11 +692,35 @@ const PartyDetails = (props: Props) => {
</section>
)
const remixSection = () => {
return (
<section className="Remixes">
<h3>{t('remixes')}</h3>
{<GridRepCollection>{renderRemixes()}</GridRepCollection>}
</section>
)
}
return (
<section className="DetailsWrapper">
<div className="PartyInfo">
<div className="Left">
<h1 className={name ? '' : 'empty'}>{name ? name : t('no_title')}</h1>
<div className="Header">
<h1 className={name ? '' : 'empty'}>
{name ? name : t('no_title')}
</h1>
{party.remix && party.sourceParty ? (
<Tooltip content={t('tooltips.source')}>
<Button
className="IconButton Blended"
leftAccessoryIcon={<RemixIcon />}
onClick={() => goTo(party.sourceParty?.shortcode)}
/>
</Tooltip>
) : (
''
)}
</div>
<div className="attribution">
{renderUserBlock()}
{party.raid ? linkedRaidBlock(party.raid) : ''}
@ -654,6 +750,7 @@ const PartyDetails = (props: Props) => {
</div>
{readOnly}
{editable}
{remixes && remixes.length > 0 ? remixSection() : ''}
{deleteAlert()}
</section>
)

View file

@ -0,0 +1,8 @@
.Tooltip {
background: var(--dialog-bg);
border-radius: $card-corner;
line-height: 1.3;
padding: $unit * 1.5;
z-index: 35;
max-width: 200px;
}

View file

@ -0,0 +1,39 @@
import React, { PropsWithChildren } from 'react'
import classNames from 'classnames'
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
import './index.scss'
interface Props extends TooltipPrimitive.TooltipContentProps {
content: React.ReactNode
open?: boolean
onOpenChange?: (open: boolean) => void
}
export default function Tooltip({
children,
content,
open,
onOpenChange,
...props
}: PropsWithChildren<Props>) {
const classes = classNames(props.className, {
Tooltip: true,
})
return (
<TooltipPrimitive.Root open={open} onOpenChange={onOpenChange}>
<TooltipPrimitive.Trigger asChild>{children}</TooltipPrimitive.Trigger>
<TooltipPrimitive.Content
side="top"
align="center"
className={classes}
sideOffset={4}
{...props}
>
{content}
{/* <TooltipPrimitive.Arrow width={11} height={5} /> */}
</TooltipPrimitive.Content>
</TooltipPrimitive.Root>
)
}

87
package-lock.json generated
View file

@ -16,6 +16,7 @@
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-toast": "^1.1.2",
"@radix-ui/react-toggle-group": "^1.0.1",
"@radix-ui/react-tooltip": "^1.0.3",
"@svgr/webpack": "^6.2.0",
"axios": "^0.25.0",
"classnames": "^2.3.1",
@ -2623,6 +2624,52 @@
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-tooltip": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.3.tgz",
"integrity": "sha512-cmc9qV4KpgqdXVTn1K8KN8MnuSXvw+E719pKwyvpCGrQ+0AA2qTjcIL3uxCj4jc4k3sDR36RF7R3H7N5hPybBQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.2",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-popper": "1.1.0",
"@radix-ui/react-portal": "1.0.1",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-slot": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.0",
"@radix-ui/react-visually-hidden": "1.0.1"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-popper": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.0.tgz",
"integrity": "sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@floating-ui/react-dom": "0.7.2",
"@radix-ui/react-arrow": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-use-callback-ref": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0",
"@radix-ui/react-use-rect": "1.0.0",
"@radix-ui/react-use-size": "1.0.0",
"@radix-ui/rect": "1.0.0"
},
"peerDependencies": {
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",
@ -9156,6 +9203,46 @@
"@radix-ui/react-use-controllable-state": "1.0.0"
}
},
"@radix-ui/react-tooltip": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.0.3.tgz",
"integrity": "sha512-cmc9qV4KpgqdXVTn1K8KN8MnuSXvw+E719pKwyvpCGrQ+0AA2qTjcIL3uxCj4jc4k3sDR36RF7R3H7N5hPybBQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@radix-ui/primitive": "1.0.0",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-dismissable-layer": "1.0.2",
"@radix-ui/react-id": "1.0.0",
"@radix-ui/react-popper": "1.1.0",
"@radix-ui/react-portal": "1.0.1",
"@radix-ui/react-presence": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-slot": "1.0.1",
"@radix-ui/react-use-controllable-state": "1.0.0",
"@radix-ui/react-visually-hidden": "1.0.1"
},
"dependencies": {
"@radix-ui/react-popper": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.0.tgz",
"integrity": "sha512-07U7jpI0dZcLRAxT7L9qs6HecSoPhDSJybF7mEGHJDBDv+ZoGCvIlva0s+WxMXwJEav+ckX3hAlXBtnHmuvlCQ==",
"requires": {
"@babel/runtime": "^7.13.10",
"@floating-ui/react-dom": "0.7.2",
"@radix-ui/react-arrow": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.0",
"@radix-ui/react-context": "1.0.0",
"@radix-ui/react-primitive": "1.0.1",
"@radix-ui/react-use-callback-ref": "1.0.0",
"@radix-ui/react-use-layout-effect": "1.0.0",
"@radix-ui/react-use-rect": "1.0.0",
"@radix-ui/react-use-size": "1.0.0",
"@radix-ui/rect": "1.0.0"
}
}
}
},
"@radix-ui/react-use-callback-ref": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz",

View file

@ -21,6 +21,7 @@
"@radix-ui/react-switch": "^1.0.1",
"@radix-ui/react-toast": "^1.1.2",
"@radix-ui/react-toggle-group": "^1.0.1",
"@radix-ui/react-tooltip": "^1.0.3",
"@svgr/webpack": "^6.2.0",
"axios": "^0.25.0",
"classnames": "^2.3.1",

View file

@ -11,6 +11,7 @@ import setUserToken from '~utils/setUserToken'
import '../styles/globals.scss'
import { ToastProvider, Viewport } from '@radix-ui/react-toast'
import { TooltipProvider } from '@radix-ui/react-tooltip'
function MyApp({ Component, pageProps }: AppProps) {
const accountCookie = getCookie('account')
@ -45,10 +46,12 @@ function MyApp({ Component, pageProps }: AppProps) {
return (
<ThemeProvider>
<ToastProvider swipeDirection="right">
<Layout>
<Component {...pageProps} />
</Layout>
<Viewport className="ToastViewport" />
<TooltipProvider>
<Layout>
<Component {...pageProps} />
</Layout>
<Viewport className="ToastViewport" />
</TooltipProvider>
</ToastProvider>
</ThemeProvider>
)

View file

@ -1,7 +1,9 @@
import React, { useEffect } from 'react'
import { useRouter } from 'next/router'
import Head from 'next/head'
import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import clonedeep from 'lodash.clonedeep'
import Party from '~components/Party'
@ -9,7 +11,7 @@ import api from '~utils/api'
import fetchLatestVersion from '~utils/fetchLatestVersion'
import organizeRaids from '~utils/organizeRaids'
import setUserToken from '~utils/setUserToken'
import { appState } from '~utils/appState'
import { appState, initialAppState } from '~utils/appState'
import { groupWeaponKeys } from '~utils/groupWeaponKeys'
import { printError } from '~utils/reportError'
@ -29,6 +31,9 @@ const NewRoute: React.FC<Props> = (props: Props) => {
// Import translations
const { t } = useTranslation('common')
// Set up router
const router = useRouter()
function callback(path: string) {
// This is scuffed, how do we do this natively?
window.history.replaceState(null, `Grid Tool`, `${path}`)
@ -38,6 +43,16 @@ const NewRoute: React.FC<Props> = (props: Props) => {
persistStaticData()
}, [persistStaticData])
useEffect(() => {
// Clean state
const resetState = clonedeep(initialAppState)
Object.keys(resetState).forEach((key) => {
appState[key] = resetState[key]
})
// Set party to be editable
appState.party.editable = true
}, [])
function persistStaticData() {
appState.raids = props.raids
appState.jobs = props.jobs
@ -47,7 +62,7 @@ const NewRoute: React.FC<Props> = (props: Props) => {
}
return (
<React.Fragment>
<React.Fragment key={router.asPath}>
<Head>
{/* HTML */}
<title>{t('page.titles.new')}</title>

View file

@ -70,7 +70,7 @@ const PartyRoute: React.FC<Props> = (props: Props) => {
}
return (
<React.Fragment>
<React.Fragment key={router.asPath}>
<Party
team={props.party}
raids={props.sortedRaids}

4
public/icons/Remix.svg Normal file
View 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="M5.50019 2.00047C5.18025 2.00132 4.83617 2.00371 4.51209 2.01042C3.93815 2.0223 3.42693 2.04775 3.22357 2.10224C2.1883 2.37964 1.37965 3.18828 1.10225 4.22356C1.04775 4.42694 1.02231 4.93829 1.01043 5.51233C1.00003 6.01504 1.00003 6.56583 1.00003 7.00001C1.00003 7.43419 1.00003 7.98498 1.01043 8.48769C1.02231 9.06174 1.04775 9.57308 1.10225 9.77647C1.36232 10.747 2.08929 11.5184 3.03219 11.8396C3.09505 11.861 3.15887 11.8805 3.22357 11.8978C3.42692 11.9523 3.9381 11.9777 4.51201 11.9896C4.7811 11.9952 5.06399 11.9978 5.33539 11.999L4.73226 12.6021C4.537 12.7974 4.537 13.1139 4.73226 13.3092C4.92752 13.5045 5.24411 13.5045 5.43937 13.3092L6.85358 11.895C6.95385 11.7947 7.00263 11.6625 6.99992 11.5311C7.00263 11.3997 6.95385 11.2674 6.85358 11.1672L5.43937 9.75295C5.24411 9.55769 4.92752 9.55769 4.73226 9.75295C4.537 9.94822 4.537 10.2648 4.73226 10.4601L5.27027 10.9981C5.01654 10.9966 4.7638 10.994 4.53092 10.9898C4.01686 10.9805 3.59961 10.9633 3.48239 10.9319C2.83534 10.7585 2.32109 10.2738 2.10695 9.64524C2.09268 9.60333 2.07974 9.56079 2.06818 9.51765C2.03675 9.40038 2.01952 8.98285 2.01025 8.4685C2.00195 8.00794 2.00003 7.46976 2.00003 7.00001C2.00003 6.53027 2.00195 5.99208 2.01025 5.53152C2.01952 5.01718 2.03676 4.59965 2.06818 4.48238C2.25311 3.79219 2.79221 3.2531 3.48239 3.06816C3.59963 3.03675 4.01694 3.01951 4.53106 3.01024C4.83429 3.00477 5.17118 3.00206 5.50006 3.00086C5.7762 2.99984 6.00003 2.77615 6.00003 2.50001C6.00003 2.22386 5.77633 1.99974 5.50019 2.00047Z"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.7765 11.8999C10.462 11.9842 9.40436 11.9981 8.52156 12.0003L8.4928 12.0004L8.48903 12.0004C8.21325 12.001 7.98986 11.7772 7.98986 11.5015C7.98986 11.2265 8.21208 11.0036 8.48675 11.0013L8.48916 11.0013L8.51572 11.0012C9.39725 10.9977 10.333 10.9834 10.5176 10.934C11.2078 10.749 11.7469 10.2099 11.9319 9.51976C11.9633 9.40248 11.9805 8.98495 11.9898 8.47061C11.9981 8.01005 12 7.47186 12 7.00212C12 6.53237 11.9981 5.99419 11.9898 5.53363C11.9805 5.01928 11.9633 4.60175 11.9319 4.48448C11.9203 4.44134 11.9074 4.3988 11.8931 4.35689C11.6789 3.72829 11.1647 3.24364 10.5176 3.07026C10.4004 3.03886 9.98317 3.02162 9.46911 3.01235C9.23664 3.00815 8.98438 3.00559 8.7311 3.00407L9.26777 3.54074C9.46303 3.736 9.46303 4.05258 9.26777 4.24784C9.0725 4.44311 8.75592 4.44311 8.56066 4.24784L7.14645 2.83363C7.04618 2.73336 6.9974 2.60111 7.00011 2.46972C6.9974 2.33833 7.04618 2.20607 7.14645 2.1058L8.56066 0.69159C8.75592 0.496328 9.0725 0.496328 9.26777 0.69159C9.46303 0.886852 9.46303 1.20343 9.26777 1.3987L8.66331 2.00316C8.93512 2.00436 9.21849 2.00695 9.48802 2.01253C10.0619 2.02441 10.5731 2.04985 10.7765 2.10434C10.8412 2.12168 10.905 2.14109 10.9678 2.1625C11.9107 2.48371 12.6377 3.25509 12.8978 4.22566C12.9523 4.42905 12.9777 4.94039 12.9896 5.51444C13 6.01715 13 6.56794 13 7.00212C13 7.43629 13 7.98709 12.9896 8.48979C12.9777 9.06384 12.9523 9.57519 12.8978 9.77857C12.6204 10.8139 11.8117 11.6225 10.7765 11.8999Z"/>
</svg>

After

Width:  |  Height:  |  Size: 3 KiB

View file

@ -39,6 +39,9 @@
"show_info": "Edit info",
"hide_info": "Hide info",
"save_info": "Save info",
"remix": "Remix",
"save": "Save",
"saved": "Saved",
"menu": "Menu",
"new": "New",
"wiki": "View more on gbf.wiki"
@ -384,6 +387,14 @@
"no_skill": "No skill"
}
},
"toasts": {
"copied": "This party's URL was copied to your clipboard"
},
"tooltips": {
"remix": "Make a copy of this team",
"save": "Save this team to your account",
"source": "Go to original team"
},
"extra_weapons": "Additional Weapons",
"equipped": "Equipped",
"coming_soon": "Coming Soon",
@ -394,5 +405,6 @@
"no_user": "Anonymous",
"no_job": "No class",
"no_value": "No value",
"remixes": "Remixes",
"level": "Level"
}

View file

@ -39,6 +39,9 @@
"show_info": "詳細を編集",
"save_info": "詳細を保存",
"hide_info": "詳細を非表示",
"remix": "リミックス",
"save": "保存する",
"saved": "保存",
"menu": "メニュー",
"new": "作成",
"wiki": "gbf.wikiで詳しく見る"
@ -385,6 +388,14 @@
"no_skill": "設定されていません"
}
},
"toasts": {
"copied": "この編成のURLはクリップボードにコピーされました"
},
"tooltips": {
"remix": "この編成をコピーする",
"save": "この編成をアカウントに保存する",
"source": "オリジナルの編成へ"
},
"equipped": "装備した",
"extra_weapons": "Additional Weapons",
"coming_soon": "開発中",
@ -395,5 +406,6 @@
"no_user": "無名",
"no_job": "ジョブなし",
"no_value": "値なし",
"remixes": "リミックスされた編成",
"level": "レベル"
}

View file

@ -153,8 +153,8 @@ $dialog--bg--dark: $grey-25;
// Color Definitions: Menu
$menu--bg--light: $grey-100;
$menu--bg--dark: $grey-10;
$menu--text--light: $grey-90;
$menu--text--dark: $grey-50;
$menu--text--light: $grey-50;
$menu--text--dark: $grey-60;
$menu--separator--light: $grey-90;
$menu--separator--dark: $grey-05;
$menu--item--bg--light--hover: $grey-85;

3
types/Party.d.ts vendored
View file

@ -18,6 +18,7 @@ interface Party {
button_count?: number
turn_count?: number
chain_count?: number
source_party?: Party
job: Job
job_skills: JobSkillObject
accessory: JobAccessory
@ -28,6 +29,8 @@ interface Party {
weapons: Array<GridWeapon>
summons: Array<GridSummon>
user: User
remix: boolean
remixes: Party[]
created_at: string
updated_at: string
}

View file

@ -120,6 +120,11 @@ class Api {
return axios.get(resourceUrl, params)
}
remix(shortcode: string, params?: {}) {
const resourceUrl = `${this.url}/parties/${shortcode}/remix`
return axios.post(resourceUrl, params)
}
savedTeams(params: {}) {
const resourceUrl = `${this.url}/parties/favorites`
return axios.get(resourceUrl, params)

View file

@ -36,6 +36,7 @@ interface AppState {
party: {
id: string | undefined
shortcode: string | undefined
editable: boolean
detailsVisible: boolean
name: string | undefined
@ -55,6 +56,9 @@ interface AppState {
extra: boolean
user: User | undefined
favorited: boolean
remix: boolean
remixes: Party[]
sourceParty?: Party
created_at: string
updated_at: string
}
@ -87,6 +91,7 @@ interface AppState {
export const initialAppState: AppState = {
party: {
id: undefined,
shortcode: '',
editable: false,
detailsVisible: false,
name: undefined,
@ -111,6 +116,9 @@ export const initialAppState: AppState = {
extra: false,
user: undefined,
favorited: false,
remix: false,
remixes: [],
sourceParty: undefined,
created_at: '',
updated_at: '',
},