Refactor
(cherry picked from commit 68aea496db8df54dba5f58da267ad962c28ef995)
This commit is contained in:
committed by
Samuel Newman
parent
84a8a31f09
commit
2016dd0993
@@ -52,6 +52,7 @@ export enum SelectedAssetError {
|
|||||||
MaxVideos = 'MaxVideos',
|
MaxVideos = 'MaxVideos',
|
||||||
VideoTooLong = 'VideoTooLong',
|
VideoTooLong = 'VideoTooLong',
|
||||||
MaxGIFs = 'MaxGIFs',
|
MaxGIFs = 'MaxGIFs',
|
||||||
|
NoGifsOnNative = 'NoGifsOnNative',
|
||||||
}
|
}
|
||||||
|
|
||||||
const SUPPORTED_VIDEO_MIME_TYPES = [
|
const SUPPORTED_VIDEO_MIME_TYPES = [
|
||||||
@@ -62,6 +63,12 @@ const SUPPORTED_VIDEO_MIME_TYPES = [
|
|||||||
] as const
|
] as const
|
||||||
export type SupportedVideoMimeType = (typeof SUPPORTED_VIDEO_MIME_TYPES)[number]
|
export type SupportedVideoMimeType = (typeof SUPPORTED_VIDEO_MIME_TYPES)[number]
|
||||||
|
|
||||||
|
function isSupportedVideoMimeType(
|
||||||
|
mimeType: string,
|
||||||
|
): mimeType is SupportedVideoMimeType {
|
||||||
|
return SUPPORTED_VIDEO_MIME_TYPES.includes(mimeType as SupportedVideoMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
const SUPPORTED_IMAGE_MIME_TYPES = (
|
const SUPPORTED_IMAGE_MIME_TYPES = (
|
||||||
[
|
[
|
||||||
'image/gif',
|
'image/gif',
|
||||||
@@ -77,6 +84,12 @@ export type SupportedImageMimeType = Exclude<
|
|||||||
boolean
|
boolean
|
||||||
>
|
>
|
||||||
|
|
||||||
|
function isSupportedImageMimeType(
|
||||||
|
mimeType: string,
|
||||||
|
): mimeType is SupportedImageMimeType {
|
||||||
|
return SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as SupportedImageMimeType)
|
||||||
|
}
|
||||||
|
|
||||||
const extensionToMimeType: Record<
|
const extensionToMimeType: Record<
|
||||||
string,
|
string,
|
||||||
SupportedVideoMimeType | SupportedImageMimeType
|
SupportedVideoMimeType | SupportedImageMimeType
|
||||||
@@ -138,27 +151,14 @@ function getImagePickerAssetType(asset: ImagePickerAsset):
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* We can now do some validation of the `mimeType` and distill it down into
|
* Distill this down into a type "group".
|
||||||
* our supported `type` groups.
|
|
||||||
*/
|
*/
|
||||||
let type: SelectedAsset['type'] | undefined
|
let type: SelectedAsset['type'] | undefined
|
||||||
if (mimeType === 'image/gif') {
|
if (mimeType === 'image/gif') {
|
||||||
type = 'gif'
|
type = 'gif'
|
||||||
} else if (mimeType?.startsWith('video/')) {
|
} else if (mimeType?.startsWith('video/')) {
|
||||||
/**
|
type = 'video'
|
||||||
* We don't care about mimeType at this point on native, since the
|
} else if (mimeType?.startsWith('image/')) {
|
||||||
* `processVideo` step later on will convert to `.mp4`.
|
|
||||||
*/
|
|
||||||
if (
|
|
||||||
!isWeb ||
|
|
||||||
SUPPORTED_VIDEO_MIME_TYPES.includes(mimeType as SupportedVideoMimeType)
|
|
||||||
) {
|
|
||||||
type = 'video'
|
|
||||||
}
|
|
||||||
} else if (
|
|
||||||
mimeType?.startsWith('image/') &&
|
|
||||||
SUPPORTED_IMAGE_MIME_TYPES.includes(mimeType as SupportedImageMimeType)
|
|
||||||
) {
|
|
||||||
type = 'image'
|
type = 'image'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,6 +203,138 @@ async function getAdditionalVideoMetadata(asset: ValidatedImagePickerAsset) {
|
|||||||
return await getVideoMetadata(file)
|
return await getVideoMetadata(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function processImagePickerAssets(
|
||||||
|
assets: ImagePickerAsset[],
|
||||||
|
{
|
||||||
|
selectionLimit,
|
||||||
|
}: {
|
||||||
|
selectionLimit: number
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* A deduped set of error codes, which we'll use later
|
||||||
|
*/
|
||||||
|
const errors = new Set<SelectedAssetError>()
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We only support selecting a single type of media at a time, so this
|
||||||
|
* gets set to whatever the first asset type is.
|
||||||
|
*/
|
||||||
|
let primaryMediaType: SelectedAsset['type'] | undefined
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This will hold the assets that we can actually use, after filtering
|
||||||
|
*/
|
||||||
|
let supportedAssets: ValidatedImagePickerAsset[] = []
|
||||||
|
|
||||||
|
for (const asset of assets) {
|
||||||
|
const {success, type, mimeType} = getImagePickerAssetType(asset)
|
||||||
|
|
||||||
|
if (!success) {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the primary media type to the first valid asset type
|
||||||
|
primaryMediaType = primaryMediaType || type
|
||||||
|
|
||||||
|
// ignore mixed types
|
||||||
|
if (type !== primaryMediaType) {
|
||||||
|
errors.add(SelectedAssetError.MixedTypes)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'video') {
|
||||||
|
/**
|
||||||
|
* We don't care too much about mimeType at this point on native,
|
||||||
|
* since the `processVideo` step later on will convert to `.mp4`.
|
||||||
|
*/
|
||||||
|
if (isWeb && !isSupportedVideoMimeType(mimeType)) {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'image') {
|
||||||
|
if (!isSupportedImageMimeType(mimeType)) {
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'gif') {
|
||||||
|
if (isNative) {
|
||||||
|
errors.add(SelectedAssetError.NoGifsOnNative)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* All validations passed, we have an asset!
|
||||||
|
*/
|
||||||
|
supportedAssets.push({
|
||||||
|
mimeType,
|
||||||
|
...asset,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (primaryMediaType === 'image') {
|
||||||
|
if (supportedAssets.length > selectionLimit) {
|
||||||
|
errors.add(SelectedAssetError.MaxImages)
|
||||||
|
supportedAssets = supportedAssets.slice(0, selectionLimit)
|
||||||
|
}
|
||||||
|
} else if (primaryMediaType === 'video') {
|
||||||
|
if (supportedAssets.length > 1) {
|
||||||
|
errors.add(SelectedAssetError.MaxVideos)
|
||||||
|
supportedAssets = supportedAssets.slice(0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedVideo = supportedAssets[0]
|
||||||
|
|
||||||
|
if (typeof selectedVideo.duration !== 'number') {
|
||||||
|
try {
|
||||||
|
const metadata = await getAdditionalVideoMetadata(selectedVideo)
|
||||||
|
selectedVideo.duration = metadata.duration
|
||||||
|
selectedVideo.width = metadata.width
|
||||||
|
selectedVideo.height = metadata.height
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.error(`processSelectedAssets: failed to get video metadata`, {
|
||||||
|
safeMessage: e.message,
|
||||||
|
})
|
||||||
|
errors.add(SelectedAssetError.Unsupported)
|
||||||
|
supportedAssets = []
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* The `duration` is in seconds on web, but in milliseconds on
|
||||||
|
* native. We normalize to milliseconds.
|
||||||
|
*/
|
||||||
|
if (isWeb) {
|
||||||
|
selectedVideo.duration = selectedVideo.duration * 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
selectedVideo.duration &&
|
||||||
|
selectedVideo.duration > VIDEO_MAX_DURATION_MS
|
||||||
|
) {
|
||||||
|
errors.add(SelectedAssetError.VideoTooLong)
|
||||||
|
supportedAssets = []
|
||||||
|
}
|
||||||
|
} else if (primaryMediaType === 'gif') {
|
||||||
|
if (supportedAssets.length > 1) {
|
||||||
|
errors.add(SelectedAssetError.MaxGIFs)
|
||||||
|
supportedAssets = supportedAssets.slice(0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: primaryMediaType!, // set above
|
||||||
|
assets: supportedAssets,
|
||||||
|
errors,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function SelectMediaBtn({
|
export function SelectMediaBtn({
|
||||||
disabled,
|
disabled,
|
||||||
selectedAssetsCount,
|
selectedAssetsCount,
|
||||||
@@ -217,98 +349,12 @@ export function SelectMediaBtn({
|
|||||||
const selectionLimit = MAX_IMAGES - selectedAssetsCount
|
const selectionLimit = MAX_IMAGES - selectedAssetsCount
|
||||||
|
|
||||||
const processSelectedAssets = useCallback(
|
const processSelectedAssets = useCallback(
|
||||||
async (assets: ImagePickerAsset[]) => {
|
async (rawAssets: ImagePickerAsset[]) => {
|
||||||
/*
|
const {
|
||||||
* A deduped set of error codes, which we'll use later
|
type,
|
||||||
*/
|
assets,
|
||||||
const errorCodes = new Set<SelectedAssetError>()
|
errors: errorCodes,
|
||||||
|
} = await processImagePickerAssets(rawAssets, {selectionLimit})
|
||||||
/*
|
|
||||||
* We only support selecting a single type of media at a time, so this
|
|
||||||
* gets set to whatever the first asset type is.
|
|
||||||
*/
|
|
||||||
let primaryMediaType: SelectedAsset['type'] | undefined
|
|
||||||
|
|
||||||
/*
|
|
||||||
* This will hold the assets that we can actually use, after filtering
|
|
||||||
*/
|
|
||||||
let supportedAssets: ValidatedImagePickerAsset[] = []
|
|
||||||
|
|
||||||
for (const asset of assets) {
|
|
||||||
const {success, type, mimeType} = getImagePickerAssetType(asset)
|
|
||||||
|
|
||||||
if (!success) {
|
|
||||||
errorCodes.add(SelectedAssetError.Unsupported)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// set the primary media type to the first valid asset type
|
|
||||||
primaryMediaType = primaryMediaType || type
|
|
||||||
|
|
||||||
// ignore mixed types
|
|
||||||
if (type !== primaryMediaType) {
|
|
||||||
errorCodes.add(SelectedAssetError.MixedTypes)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
supportedAssets.push({
|
|
||||||
mimeType,
|
|
||||||
...asset,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (primaryMediaType === 'image') {
|
|
||||||
if (supportedAssets.length > selectionLimit) {
|
|
||||||
errorCodes.add(SelectedAssetError.MaxImages)
|
|
||||||
supportedAssets = supportedAssets.slice(0, selectionLimit)
|
|
||||||
}
|
|
||||||
} else if (primaryMediaType === 'video') {
|
|
||||||
if (supportedAssets.length > 1) {
|
|
||||||
errorCodes.add(SelectedAssetError.MaxVideos)
|
|
||||||
supportedAssets = supportedAssets.slice(0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedVideo = supportedAssets[0]
|
|
||||||
|
|
||||||
if (typeof selectedVideo.duration !== 'number') {
|
|
||||||
try {
|
|
||||||
const metadata = await getAdditionalVideoMetadata(selectedVideo)
|
|
||||||
selectedVideo.duration = metadata.duration
|
|
||||||
selectedVideo.width = metadata.width
|
|
||||||
selectedVideo.height = metadata.height
|
|
||||||
} catch (e: any) {
|
|
||||||
logger.error(
|
|
||||||
`processSelectedAssets: failed to get video metadata`,
|
|
||||||
{
|
|
||||||
safeMessage: e.message,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
errorCodes.add(SelectedAssetError.Unsupported)
|
|
||||||
supportedAssets = []
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* The `duration` is in seconds on web, but in milliseconds on
|
|
||||||
* native. We normalize to milliseconds.
|
|
||||||
*/
|
|
||||||
if (isWeb) {
|
|
||||||
selectedVideo.duration = selectedVideo.duration * 1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
selectedVideo.duration &&
|
|
||||||
selectedVideo.duration > VIDEO_MAX_DURATION_MS
|
|
||||||
) {
|
|
||||||
errorCodes.add(SelectedAssetError.VideoTooLong)
|
|
||||||
supportedAssets = []
|
|
||||||
}
|
|
||||||
} else if (primaryMediaType === 'gif') {
|
|
||||||
if (supportedAssets.length > 1) {
|
|
||||||
errorCodes.add(SelectedAssetError.MaxGIFs)
|
|
||||||
supportedAssets = supportedAssets.slice(0, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert error codes to user-friendly messages.
|
* Convert error codes to user-friendly messages.
|
||||||
@@ -333,16 +379,19 @@ export function SelectMediaBtn({
|
|||||||
[SelectedAssetError.MaxGIFs]: _(
|
[SelectedAssetError.MaxGIFs]: _(
|
||||||
msg`You can only select one GIF at a time.`,
|
msg`You can only select one GIF at a time.`,
|
||||||
),
|
),
|
||||||
|
[SelectedAssetError.NoGifsOnNative]: _(
|
||||||
|
msg`GIFs are only supported on web at this time.`,
|
||||||
|
),
|
||||||
}[error]
|
}[error]
|
||||||
})
|
})
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Finally, report the selected assets and any errors back to the
|
* Report the selected assets and any errors back to the
|
||||||
* composer.
|
* composer.
|
||||||
*/
|
*/
|
||||||
onSelectAssets({
|
onSelectAssets({
|
||||||
type: primaryMediaType!,
|
type,
|
||||||
assets: supportedAssets,
|
assets,
|
||||||
errors,
|
errors,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user