Skip to content

Commit 6a54b3a

Browse files
[C-2742] Multi-track form pagination (#3818)
Co-authored-by: Dylan Jeffers <[email protected]>
1 parent 65471a1 commit 6a54b3a

15 files changed

+239
-102
lines changed

packages/web/src/components/data-entry/TagInput.d.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export type TagInputProps = {
22
placeholder?: string
3-
defaultTags: string[]
3+
defaultTags?: string[]
4+
// For controlled input
5+
tags?: Set<string>
46
maxTags?: number
57
maxCharacters?: number
68
minCharacters?: number

packages/web/src/components/data-entry/TagInput.js

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class TagInput extends Component {
3838
if (this.state.typingMode && this.newTagInputRef.current) {
3939
this.newTagInputRef.current.focus()
4040
}
41+
if (this.state.tags !== this.props.tags) {
42+
this.setState({ tags: this.props.tags })
43+
}
4144
}
4245

4346
setTypingMode = () => {

packages/web/src/components/form-fields/TagField.tsx

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback } from 'react'
1+
import { useCallback, useMemo } from 'react'
22

33
import { removeNullable } from '@audius/common'
44
import { useField } from 'formik'
@@ -10,10 +10,14 @@ type TagFieldProps = Partial<TagInputProps> & {
1010
}
1111
export const TagField = (props: TagFieldProps) => {
1212
const { name, ...other } = props
13-
const [field, , { setValue }] = useField(name)
13+
const [field, , { setValue }] = useField<string>(name)
1414
const { value, ...otherField } = field
1515

16-
const tags = value.split(',').filter(removeNullable)
16+
const tagList = useMemo(
17+
() => (value ?? '').split(',').filter(removeNullable),
18+
[value]
19+
)
20+
const tagSet = useMemo(() => new Set(value ? tagList : []), [tagList, value])
1721

1822
const handleChangeTags = useCallback(
1923
(value: Set<string>) => setValue([...value].join(',')),
@@ -22,7 +26,7 @@ export const TagField = (props: TagFieldProps) => {
2226

2327
return (
2428
<TagInput
25-
defaultTags={tags}
29+
tags={tagSet}
2630
{...otherField}
2731
onChangeTags={handleChangeTags}
2832
{...other}

packages/web/src/components/form-fields/TextAreaField.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ type TextAreaFieldProps = TextAreaV2Props & {
88

99
export const TextAreaField = (props: TextAreaFieldProps) => {
1010
const { name, ...other } = props
11-
const [field, meta] = useField(name)
11+
const [{ value, ...field }, meta] = useField(name)
1212

1313
const hasError = Boolean(meta.touched && meta.error)
1414

15-
return <TextAreaV2 {...field} error={hasError} {...other} />
15+
return <TextAreaV2 value={value} {...field} error={hasError} {...other} />
1616
}

packages/web/src/pages/upload-page/components/EditPageNew.tsx

+44-36
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import { useCallback, useMemo } from 'react'
22

3-
import { ExtendedTrackMetadata, Nullable } from '@audius/common'
4-
import { Button, ButtonType, IconArrow } from '@audius/stems'
3+
import { HarmonyButton, HarmonyButtonType, IconArrow } from '@audius/stems'
4+
import cn from 'classnames'
55
import { Form, Formik } from 'formik'
66
import moment from 'moment'
77
import * as Yup from 'yup'
88

9+
import layoutStyles from 'components/layout/layout.module.css'
910
import PreviewButton from 'components/upload/PreviewButton'
1011

12+
import { MultiTrackSidebar } from '../fields/MultiTrackSidebar'
1113
import { TrackMetadataFields } from '../fields/TrackMetadataFields'
14+
import { defaultHiddenFields } from '../fields/availability/HiddenAvailabilityFields'
15+
import { TrackEditFormValues } from '../forms/types'
1216

1317
import styles from './EditPageNew.module.css'
1418
import { TrackModalArray } from './TrackModalArray'
@@ -25,14 +29,6 @@ type EditPageProps = {
2529
setTracks: (tracks: TrackForUpload[]) => void
2630
onContinue: () => void
2731
}
28-
export type EditFormValues = ExtendedTrackMetadata & {
29-
releaseDate: moment.Moment
30-
licenseType: {
31-
allowAttribution: Nullable<boolean>
32-
commercialUse: Nullable<boolean>
33-
derivativeWorks: Nullable<boolean>
34-
}
35-
}
3632

3733
const EditTrackSchema = Yup.object().shape({
3834
title: Yup.string().required(messages.titleError),
@@ -47,53 +43,65 @@ const EditTrackSchema = Yup.object().shape({
4743
export const EditPageNew = (props: EditPageProps) => {
4844
const { tracks, setTracks, onContinue } = props
4945

50-
const [{ metadata: trackMetadata }] = tracks
51-
52-
const initialValues: EditFormValues = useMemo(
46+
const initialValues: TrackEditFormValues = useMemo(
5347
() => ({
54-
...trackMetadata,
55-
artwork: null,
56-
description: '',
57-
releaseDate: moment().startOf('day'),
58-
tags: '',
59-
licenseType: {
60-
allowAttribution: null,
61-
commercialUse: null,
62-
derivativeWorks: null
63-
}
48+
trackMetadatasIndex: 0,
49+
trackMetadatas: tracks.map((track) => ({
50+
...track.metadata,
51+
artwork: null,
52+
description: '',
53+
releaseDate: moment().startOf('day'),
54+
tags: '',
55+
field_visibility: {
56+
...defaultHiddenFields,
57+
remixes: true
58+
},
59+
licenseType: {
60+
allowAttribution: null,
61+
commercialUse: null,
62+
derivativeWorks: null
63+
}
64+
}))
6465
}),
65-
[trackMetadata]
66+
[tracks]
6667
)
6768

6869
const onSubmit = useCallback(
69-
(values: EditFormValues) => {
70-
setTracks([{ ...tracks[0], metadata: values }])
70+
(values: TrackEditFormValues) => {
71+
const tracksForUpload: TrackForUpload[] = tracks.map((track, i) => ({
72+
...track,
73+
metadata: values.trackMetadatas[i]
74+
}))
75+
setTracks(tracksForUpload)
7176
onContinue()
7277
},
7378
[onContinue, setTracks, tracks]
7479
)
7580

81+
const isMultiTrack = tracks.length > 1
82+
7683
return (
77-
<Formik<EditFormValues>
84+
<Formik<TrackEditFormValues>
7885
initialValues={initialValues}
7986
onSubmit={onSubmit}
8087
validationSchema={EditTrackSchema}
8188
>
8289
{() => (
8390
<Form>
84-
<div className={styles.editForm}>
85-
<TrackMetadataFields />
86-
<TrackModalArray />
87-
<PreviewButton playing={false} onClick={() => {}} />
91+
<div className={cn(layoutStyles.row, layoutStyles.gap2)}>
92+
<div className={styles.editForm}>
93+
<TrackMetadataFields playing={false} />
94+
<TrackModalArray />
95+
<PreviewButton playing={false} onClick={() => {}} />
96+
</div>
97+
{isMultiTrack ? <MultiTrackSidebar tracks={tracks} /> : null}
8898
</div>
8999
<div className={styles.continue}>
90-
<Button
91-
type={ButtonType.PRIMARY_ALT}
92-
buttonType='submit'
100+
<HarmonyButton
101+
variant={HarmonyButtonType.PRIMARY}
93102
text='Continue'
94103
name='continue'
95-
rightIcon={<IconArrow />}
96-
textClassName={styles.continueButtonText}
104+
iconRight={IconArrow}
97105
className={styles.continueButton}
98106
/>
99107
</div>

packages/web/src/pages/upload-page/fields/DatePickerField.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ export const DatePickerField = (props: DatePickerFieldProps) => {
7070
// @ts-ignore mismatched moment versions; shouldn't be relevant here
7171
initialVisibleMonth={() => moment()} // PropTypes.func or null,
7272
hideKeyboardShortcutsPanel
73-
small
7473
noBorder
7574
/>
7675
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { HarmonyButton } from '@audius/stems'
2+
import cn from 'classnames'
3+
import { useField } from 'formik'
4+
5+
import layoutStyles from 'components/layout/layout.module.css'
6+
7+
import { TrackForUpload } from '../components/types'
8+
9+
type MultiTrackSidebarProps = {
10+
tracks: TrackForUpload[]
11+
}
12+
13+
export const MultiTrackSidebar = (props: MultiTrackSidebarProps) => {
14+
const { tracks } = props
15+
const limit = tracks.length
16+
const [{ value: index }, , { setValue: setIndex }] = useField(
17+
'trackMetadatasIndex'
18+
)
19+
20+
return (
21+
<div className={cn(layoutStyles.col, layoutStyles.gap2)}>
22+
Track {index + 1} of {limit}
23+
<div className={cn(layoutStyles.row, layoutStyles.gap2)}>
24+
<HarmonyButton
25+
text={'Prev'}
26+
onClick={() => setIndex(Math.max(index - 1, 0))}
27+
type='button'
28+
/>
29+
<HarmonyButton
30+
text={'Next'}
31+
onClick={() => setIndex(Math.min(index + 1, limit - 1))}
32+
type='button'
33+
/>
34+
</div>
35+
</div>
36+
)
37+
}

packages/web/src/pages/upload-page/fields/TrackMetadataFields.tsx

+18-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { GENRES } from '@audius/common'
2+
import { useField } from 'formik'
23

34
import { InputV2Variant } from 'components/data-entry/InputV2'
45
import {
@@ -10,6 +11,8 @@ import {
1011
} from 'components/form-fields'
1112
import { moodMap } from 'utils/Moods'
1213

14+
import { getTrackFieldName } from '../forms/utils'
15+
1316
import styles from './TrackMetadataFields.module.css'
1417

1518
const MOODS = Object.keys(moodMap).map((k) => ({
@@ -24,16 +27,23 @@ const messages = {
2427
description: 'Description'
2528
}
2629

27-
export const TrackMetadataFields = () => {
30+
type TrackMetadataFieldsProps = {
31+
/** Whether or not the preview is playing. */
32+
playing: boolean
33+
}
34+
35+
export const TrackMetadataFields = (props: TrackMetadataFieldsProps) => {
36+
const [{ value: index }] = useField('trackMetadatasIndex')
37+
2838
return (
2939
<div className={styles.basic}>
3040
<div className={styles.artwork}>
31-
<ArtworkField name='artwork' />
41+
<ArtworkField name={getTrackFieldName(index, 'artwork')} />
3242
</div>
3343
<div className={styles.fields}>
3444
<div className={styles.trackName}>
3545
<TextField
36-
name='title'
46+
name={getTrackFieldName(index, 'title')}
3747
variant={InputV2Variant.ELEVATED_PLACEHOLDER}
3848
label={messages.trackName}
3949
maxLength={64}
@@ -42,27 +52,28 @@ export const TrackMetadataFields = () => {
4252
</div>
4353
<div className={styles.categorization}>
4454
<DropdownField
45-
name='genre'
55+
name={getTrackFieldName(index, 'genre')}
4656
aria-label={messages.genre}
4757
placeholder={messages.genre}
4858
mount='parent'
4959
menu={{ items: GENRES }}
5060
size='large'
5161
/>
5262
<DropdownField
53-
name='mood'
63+
name={getTrackFieldName(index, 'mood')}
64+
aria-label={messages.mood}
5465
placeholder={messages.mood}
5566
mount='parent'
5667
menu={{ items: MOODS }}
5768
size='large'
5869
/>
5970
</div>
6071
<div className={styles.tags}>
61-
<TagField name='tags' />
72+
<TagField name={getTrackFieldName(index, 'tags')} />
6273
</div>
6374
<div className={styles.description}>
6475
<TextAreaField
65-
name='description'
76+
name={getTrackFieldName(index, 'description')}
6677
className={styles.textArea}
6778
placeholder={messages.description}
6879
maxLength={1000}

0 commit comments

Comments
 (0)