Skip to content

Commit

Permalink
[PAY-2458][PAY-2463][PAY-2474] Add lossless downloads edit flow and o…
Browse files Browse the repository at this point in the history
…ther fixes (#7590)

Co-authored-by: Saliou Diallo <[email protected]>
  • Loading branch information
sddioulde and Saliou Diallo authored Feb 14, 2024
1 parent 3bcf175 commit 570d049
Show file tree
Hide file tree
Showing 22 changed files with 630 additions and 65 deletions.
4 changes: 2 additions & 2 deletions packages/common/src/hooks/useDownloadTrackButtons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,8 @@ export const useUploadingStems = ({ trackId }: { trackId: ID }) => {
shallowEqual
)
const uploadingTracks = currentUploads.map((u) => ({
name: u.file.name,
size: u.file.size,
name: u.file?.name ?? '', // the file should always exist here
size: u.file?.size ?? 0, // the file should always exist here
category: u.category,
downloadable: false
}))
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/models/Stems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,5 @@ export type StemUpload = {
}

export type StemUploadWithFile = StemUpload & {
file: File
file?: File
}
3 changes: 2 additions & 1 deletion packages/common/src/utils/fileUtils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Buffer } from 'buffer'

import { Nullable } from './typeUtils'
import { DownloadFile } from '~/services'

import { Nullable } from './typeUtils'

/** Convert a base64 string to a file object */
export const dataURLtoFile = async (
dataUrl: string,
Expand Down
1 change: 0 additions & 1 deletion packages/discovery-provider/src/tasks/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,6 @@ class PlaylistMetadata(TypedDict):
"track_cid",
"orig_file_cid",
"orig_filename",
"is_original_available",
"duration",
"is_available",
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@
bottom: 0;
}

.tab > input:disabled {
cursor: auto;
}

.separator {
opacity: 1;
width: 1px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ export const SegmentedControl = <T extends string>(
onChange={() => {
onSetSelected(option.key)
}}
disabled={props.disabled}
disabled={props.disabled || option.disabled}
/>
{option.text}
</label>
Expand Down
1 change: 1 addition & 0 deletions packages/harmony/src/components/segmented-control/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type Option<T> = {
key: T
text: string
icon?: React.ReactNode
disabled?: boolean
}

export type SegmentedControlProps<T extends string> = {
Expand Down
2 changes: 1 addition & 1 deletion packages/web/src/common/store/cache/tracks/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ import {
confirmTransaction
} from '@audius/common/store'
import {
formatUrlName,
makeKindId,
squashNewLines,
formatUrlName,
waitForAccount,
waitForValue
} from '@audius/common/utils'
Expand Down
127 changes: 111 additions & 16 deletions packages/web/src/components/data-entry/AccessAndSaleTriggerLegacy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
TipGatedConditions,
USDCPurchaseConditions,
AccessConditions,
Track
Track,
Download
} from '@audius/common/models'
import { accountSelectors } from '@audius/common/store'
import { Nullable } from '@audius/common/utils'
Expand All @@ -34,9 +35,15 @@ import { AccessAndSaleMenuFields } from 'pages/upload-page/fields/AccessAndSaleM
import { getCombinedDefaultGatedConditionValues } from 'pages/upload-page/fields/helpers'
import {
AccessAndSaleFormValues,
DOWNLOAD_CONDITIONS,
DOWNLOAD_REQUIRES_FOLLOW,
FIELD_VISIBILITY,
GateKeeper,
IS_DOWNLOADABLE,
IS_DOWNLOAD_GATED,
IS_STREAM_GATED,
IS_UNLISTED,
LAST_GATE_KEEPER,
PREVIEW,
PRICE_HUMANIZED,
SPECIAL_ACCESS_TYPE,
Expand Down Expand Up @@ -64,7 +71,11 @@ const messages = {
enum GatedTrackMetadataField {
IS_STREAM_GATED = 'is_stream_gated',
STREAM_CONDITIONS = 'stream_conditions',
PREVIEW = 'preview_start_seconds'
PREVIEW = 'preview_start_seconds',
IS_DOWNLOAD_GATED = 'is_download_gated',
DOWNLOAD_CONDITIONS = 'download_conditions',
IS_DOWNLOADABLE = 'is_downloadable',
DOWNLOAD = 'download'
}

enum UnlistedTrackMetadataField {
Expand All @@ -81,6 +92,10 @@ type TrackMetadataState = {
[GatedTrackMetadataField.IS_STREAM_GATED]: boolean
[GatedTrackMetadataField.STREAM_CONDITIONS]: Nullable<AccessConditions>
[GatedTrackMetadataField.PREVIEW]: Nullable<number>
[GatedTrackMetadataField.IS_DOWNLOAD_GATED]: boolean
[GatedTrackMetadataField.DOWNLOAD_CONDITIONS]: Nullable<AccessConditions>
[GatedTrackMetadataField.IS_DOWNLOADABLE]: boolean
[GatedTrackMetadataField.DOWNLOAD]: Download
[UnlistedTrackMetadataField.SCHEDULED_RELEASE]: boolean
[UnlistedTrackMetadataField.UNLISTED]: boolean
[UnlistedTrackMetadataField.GENRE]: boolean
Expand All @@ -97,6 +112,10 @@ type AccessAndSaleTriggerLegacyProps = {
metadataState: TrackMetadataState
trackLength: number
didUpdateState: (newState: TrackMetadataState) => void
lastGateKeeper: GateKeeper
setLastGateKeeper: (value: GateKeeper) => void
forceOpen?: boolean
setForceOpen?: (value: boolean) => void
}

export const AccessAndSaleTriggerLegacy = (
Expand All @@ -108,7 +127,11 @@ export const AccessAndSaleTriggerLegacy = (
initialForm,
metadataState,
trackLength,
didUpdateState
didUpdateState,
lastGateKeeper,
setLastGateKeeper,
forceOpen,
setForceOpen
} = props
const initialStreamConditions = initialForm[STREAM_CONDITIONS]
const {
Expand All @@ -117,8 +140,13 @@ export const AccessAndSaleTriggerLegacy = (
scheduled_release: isScheduledRelease,
is_stream_gated: isStreamGated,
preview_start_seconds: preview,
is_download_gated: isDownloadGated,
download_conditions: downloadConditions,
is_downloadable: isDownloadable,
download,
...fieldVisibility
} = metadataState

/**
* Stream conditions from inside the modal.
* Upon submit, these values along with the selected access option will
Expand All @@ -145,6 +173,15 @@ export const AccessAndSaleTriggerLegacy = (
set(initialValues, IS_UNLISTED, isUnlisted)
set(initialValues, IS_STREAM_GATED, isStreamGated)
set(initialValues, STREAM_CONDITIONS, tempStreamConditions)
set(initialValues, IS_DOWNLOAD_GATED, isDownloadGated)
set(initialValues, DOWNLOAD_CONDITIONS, downloadConditions)
set(initialValues, IS_DOWNLOADABLE, isDownloadable)
set(
initialValues,
DOWNLOAD_REQUIRES_FOLLOW,
isContentFollowGated(downloadConditions)
)
set(initialValues, LAST_GATE_KEEPER, lastGateKeeper ?? {})

let availabilityType = StreamTrackAvailabilityType.PUBLIC
if (isUsdcGated) {
Expand Down Expand Up @@ -179,14 +216,18 @@ export const AccessAndSaleTriggerLegacy = (
)
return initialValues as AccessAndSaleFormValues
}, [
fieldVisibility,
isStreamGated,
isUnlisted,
savedStreamConditions,
isUnlisted,
isStreamGated,
tempStreamConditions,
initialStreamConditions,
isDownloadGated,
downloadConditions,
isDownloadable,
lastGateKeeper,
isScheduledRelease,
fieldVisibility,
preview,
isScheduledRelease
initialStreamConditions
])

const onSubmit = (values: AccessAndSaleFormValues) => {
Expand All @@ -195,6 +236,7 @@ export const AccessAndSaleTriggerLegacy = (
const specialAccessType = get(values, SPECIAL_ACCESS_TYPE)
const fieldVisibility = get(values, FIELD_VISIBILITY)
const streamConditions = get(values, STREAM_CONDITIONS)
const lastGateKeeper = get(values, LAST_GATE_KEEPER)

let newState = {
...metadataState,
Expand All @@ -209,33 +251,70 @@ export const AccessAndSaleTriggerLegacy = (
// For gated options, extract the correct stream conditions based on the selected availability type
switch (availabilityType) {
case StreamTrackAvailabilityType.USDC_PURCHASE: {
newState.preview_start_seconds = preview ?? 0
const {
usdc_purchase: { price }
} = streamConditions as USDCPurchaseConditions
newState.stream_conditions = {
const conditions = {
// @ts-ignore splits get added in saga
usdc_purchase: { price: Math.round(price) }
}
usdc_purchase: {
price: Math.round(
(streamConditions as USDCPurchaseConditions).usdc_purchase.price
)
}
} as USDCPurchaseConditions
newState.is_stream_gated = true
newState.stream_conditions = conditions
newState.preview_start_seconds = preview ?? 0
newState.is_download_gated = true
newState.download_conditions = conditions
newState.is_downloadable = true
newState.download = { ...download, requires_follow: false }
const downloadableGateKeeper =
isDownloadable && lastGateKeeper.downloadable === 'stemsAndDownloads'
? 'stemsAndDownloads'
: 'accessAndSale'
setLastGateKeeper({
...lastGateKeeper,
access: 'accessAndSale',
downloadable: downloadableGateKeeper
})
break
}
case StreamTrackAvailabilityType.SPECIAL_ACCESS: {
if (specialAccessType === SpecialAccessType.FOLLOW) {
const { follow_user_id } = streamConditions as FollowGatedConditions
newState.stream_conditions = { follow_user_id }
newState.download_conditions = { follow_user_id }
if (isDownloadable) {
newState.download = { ...download, requires_follow: true }
}
} else {
const { tip_user_id } = streamConditions as TipGatedConditions
newState.stream_conditions = { tip_user_id }
newState.download_conditions = { tip_user_id }
if (isDownloadable) {
newState.download = { ...download, requires_follow: false }
}
}
newState.is_stream_gated = true
newState.is_download_gated = true
setLastGateKeeper({
...lastGateKeeper,
access: 'accessAndSale'
})
break
}
case StreamTrackAvailabilityType.COLLECTIBLE_GATED: {
const { nft_collection } =
streamConditions as CollectibleGatedConditions
newState.stream_conditions = { nft_collection }
newState.is_stream_gated = true
newState.stream_conditions = { nft_collection }
newState.is_download_gated = true
newState.download_conditions = { nft_collection }
if (isDownloadable) {
newState.download = { ...download, requires_follow: false }
}
setLastGateKeeper({
...lastGateKeeper,
access: 'accessAndSale'
})
break
}
case StreamTrackAvailabilityType.HIDDEN: {
Expand All @@ -245,9 +324,23 @@ export const AccessAndSaleTriggerLegacy = (
remixes: fieldVisibility?.remixes ?? defaultFieldVisibility.remixes,
unlisted: true
}
if (lastGateKeeper.access === 'accessAndSale') {
newState.is_download_gated = false
newState.download_conditions = null
}
if (lastGateKeeper.downloadable === 'accessAndSale') {
newState.is_downloadable = false
}
break
}
case StreamTrackAvailabilityType.PUBLIC: {
if (lastGateKeeper.access === 'accessAndSale') {
newState.is_download_gated = false
newState.download_conditions = null
}
if (lastGateKeeper.downloadable === 'accessAndSale') {
newState.is_downloadable = false
}
break
}
}
Expand Down Expand Up @@ -293,6 +386,8 @@ export const AccessAndSaleTriggerLegacy = (
isScheduledRelease={isScheduledRelease}
/>
}
forceOpen={forceOpen}
setForceOpen={setForceOpen}
renderValue={() => null}
previewOverride={(toggleMenu) => (
<Button
Expand Down
2 changes: 2 additions & 0 deletions packages/web/src/components/data-entry/ContextualMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const MenuForm = (props: MenuFormProps) => {
}, [displayMenuErrorMessage, errors])

useEffect(() => {
// If the menu is closed, trigger callback if exists and reset the status
if (!isOpen) {
closeMenuCallback?.(status)
setStatus(initialStatus)
Expand Down Expand Up @@ -171,6 +172,7 @@ export const ContextualMenu = <FormValues extends FormikValues = FormikValues>(
const [isMenuOpen, toggleMenu] = useToggle(false)

useEffect(() => {
// If forceOpen is true, open the menu and reset the forceOpen flag
if (forceOpen && setForceOpen) {
setForceOpen(false)
toggleMenu()
Expand Down
Loading

0 comments on commit 570d049

Please sign in to comment.