diff --git a/examples/getstarted/src/api/address/content-types/address/schema.json b/examples/getstarted/src/api/address/content-types/address/schema.json index f070cb926e..90bfbaa50f 100755 --- a/examples/getstarted/src/api/address/content-types/address/schema.json +++ b/examples/getstarted/src/api/address/content-types/address/schema.json @@ -32,7 +32,8 @@ "allowedTypes": [ "files", "images", - "videos" + "videos", + "audios" ], "pluginOptions": {} }, diff --git a/packages/core/content-type-builder/admin/src/components/AllowedTypesSelect/index.js b/packages/core/content-type-builder/admin/src/components/AllowedTypesSelect/index.js index 8970a465ee..9fc0575f56 100644 --- a/packages/core/content-type-builder/admin/src/components/AllowedTypesSelect/index.js +++ b/packages/core/content-type-builder/admin/src/components/AllowedTypesSelect/index.js @@ -11,7 +11,8 @@ const options = [ children: [ { label: 'images (JPEG, PNG, GIF, SVG, TIFF, ICO, DVU)', value: 'images' }, { label: 'videos (MPEG, MP4, Quicktime, WMV, AVI, FLV)', value: 'videos' }, - { label: 'files (CSV, ZIP, MP3, PDF, Excel, JSON, ...)', value: 'files' }, + { label: 'audios (MP3, WAV, OGG)', value: 'audios' }, + { label: 'files (CSV, ZIP, PDF, Excel, JSON, ...)', value: 'files' }, ], }, ]; diff --git a/packages/core/content-type-builder/admin/src/components/FormModal/attributes/types.js b/packages/core/content-type-builder/admin/src/components/FormModal/attributes/types.js index 80e333c9f4..524098f7cf 100644 --- a/packages/core/content-type-builder/admin/src/components/FormModal/attributes/types.js +++ b/packages/core/content-type-builder/admin/src/components/FormModal/attributes/types.js @@ -215,7 +215,7 @@ const types = { required: validators.required(), allowedTypes: yup .array() - .of(yup.string().oneOf(['images', 'videos', 'files'])) + .of(yup.string().oneOf(['images', 'videos', 'files', 'audios'])) .min(1) .nullable(), }; diff --git a/packages/core/content-type-builder/admin/src/components/FormModal/reducer.js b/packages/core/content-type-builder/admin/src/components/FormModal/reducer.js index ec597d55eb..23970b673b 100644 --- a/packages/core/content-type-builder/admin/src/components/FormModal/reducer.js +++ b/packages/core/content-type-builder/admin/src/components/FormModal/reducer.js @@ -266,7 +266,7 @@ const reducer = (state = initialState, action) => dataToSet = options; } else if (attributeType === 'media') { dataToSet = { - allowedTypes: ['images', 'files', 'videos'], + allowedTypes: ['images', 'files', 'videos', 'audios'], type: 'media', multiple: true, ...options, diff --git a/packages/core/content-type-builder/admin/src/components/FormModal/tests/reducer.test.js b/packages/core/content-type-builder/admin/src/components/FormModal/tests/reducer.test.js index 5da1c3cb13..69e894bbf4 100644 --- a/packages/core/content-type-builder/admin/src/components/FormModal/tests/reducer.test.js +++ b/packages/core/content-type-builder/admin/src/components/FormModal/tests/reducer.test.js @@ -582,7 +582,7 @@ describe('CTB | components | FormModal | reducer | actions', () => { modifiedData: { type: 'media', multiple: true, - allowedTypes: ['images', 'files', 'videos'], + allowedTypes: ['images', 'files', 'videos', 'audios'], }, }; diff --git a/packages/core/content-type-builder/admin/src/pages/ListView/tests/mockData.js b/packages/core/content-type-builder/admin/src/pages/ListView/tests/mockData.js index 838be6e05d..e0527a420c 100644 --- a/packages/core/content-type-builder/admin/src/pages/ListView/tests/mockData.js +++ b/packages/core/content-type-builder/admin/src/pages/ListView/tests/mockData.js @@ -36,7 +36,7 @@ export default { type: 'media', multiple: false, required: false, - allowedTypes: ['files', 'images', 'videos'], + allowedTypes: ['files', 'images', 'videos', 'audios'], pluginOptions: { i18n: { localized: true, diff --git a/packages/core/content-type-builder/server/controllers/validation/types.js b/packages/core/content-type-builder/server/controllers/validation/types.js index 735f4ea6e4..eef05d9135 100644 --- a/packages/core/content-type-builder/server/controllers/validation/types.js +++ b/packages/core/content-type-builder/server/controllers/validation/types.js @@ -53,7 +53,7 @@ const getTypeShape = (attribute, { modelType, attributes } = {}) => { required: validators.required, allowedTypes: yup .array() - .of(yup.string().oneOf(['images', 'videos', 'files'])) + .of(yup.string().oneOf(['images', 'videos', 'files', 'audios'])) .min(1), }; } diff --git a/packages/core/upload/admin/src/components/AssetCard/AssetCard.js b/packages/core/upload/admin/src/components/AssetCard/AssetCard.js index a05529b97d..7c8d8071fd 100644 --- a/packages/core/upload/admin/src/components/AssetCard/AssetCard.js +++ b/packages/core/upload/admin/src/components/AssetCard/AssetCard.js @@ -4,6 +4,7 @@ import { prefixFileUrlWithBackendUrl, getFileExtension } from '@strapi/helper-pl import { ImageAssetCard } from './ImageAssetCard'; import { VideoAssetCard } from './VideoAssetCard'; import { DocAssetCard } from './DocAssetCard'; +import { AudioAssetCard } from './AudioAssetCard'; import { AssetType, AssetDefinition } from '../../constants'; import { createAssetUrl } from '../../utils/createAssetUrl'; import toSingularTypes from '../../utils/toSingularTypes'; @@ -64,7 +65,31 @@ export const AssetCard = ({ ); } - const canSelectAsset = singularTypes.includes('file') && !['video', 'image'].includes(fileType); + if (asset.mime.includes(AssetType.Audio)) { + const canSelectAsset = singularTypes.includes(fileType); + + if (!canSelectAsset && !isSelected) { + handleSelect = undefined; + } + + return ( + onEdit(asset) : undefined} + onSelect={handleSelect} + selected={isSelected} + size={size} + /> + ); + } + + const canSelectAsset = + singularTypes.includes('file') && !['video', 'image', 'audio'].includes(fileType); if (!canSelectAsset && !isSelected) { handleSelect = undefined; @@ -74,7 +99,7 @@ export const AssetCard = ({ }; AssetCard.defaultProps = { - allowedTypes: ['images', 'files', 'videos'], + allowedTypes: ['images', 'files', 'videos', 'audios'], isSelected: false, // Determine if the asset is loaded locally or from a remote resource local: false, diff --git a/packages/core/upload/admin/src/components/AssetCard/AudioAssetCard.js b/packages/core/upload/admin/src/components/AssetCard/AudioAssetCard.js new file mode 100644 index 0000000000..aa1475312f --- /dev/null +++ b/packages/core/upload/admin/src/components/AssetCard/AudioAssetCard.js @@ -0,0 +1,100 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import styled from 'styled-components'; +import { + Card, + CardAction, + CardAsset, + CardBadge, + CardBody, + CardCheckbox, + CardContent, + CardHeader, + CardTitle, + CardSubtitle, +} from '@strapi/design-system/Card'; +import { IconButton } from '@strapi/design-system/IconButton'; +import Pencil from '@strapi/icons/Pencil'; +import { useIntl } from 'react-intl'; +import { Box } from '@strapi/design-system/Box'; +import { AudioPreview } from './AudioPreview'; +import { getTrad } from '../../utils'; + +const Extension = styled.span` + text-transform: uppercase; +`; + +const AudioPreviewWrapper = styled(Box)` + canvas, + audio { + display: block; + max-width: 100%; + max-height: ${({ size }) => (size === 'M' ? 164 / 16 : 88 / 16)}rem; + } +`; + +export const AudioAssetCard = ({ + name, + extension, + url, + mime, + selected, + onSelect, + onEdit, + size, +}) => { + const { formatMessage } = useIntl(); + + return ( + + + + + + + + {onSelect && } + {onEdit && ( + + } + onClick={onEdit} + /> + + )} + + + + + {name} + + + {extension} + + + + {formatMessage({ id: getTrad('settings.section.audio.label'), defaultMessage: 'Audio' })} + + + + ); +}; + +AudioAssetCard.defaultProps = { + onSelect: undefined, + onEdit: undefined, + selected: false, + size: 'M', +}; + +AudioAssetCard.propTypes = { + extension: PropTypes.string.isRequired, + mime: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + onSelect: PropTypes.func, + onEdit: PropTypes.func, + url: PropTypes.string.isRequired, + selected: PropTypes.bool, + size: PropTypes.oneOf(['S', 'M']), +}; diff --git a/packages/core/upload/admin/src/components/AssetCard/AudioPreview.js b/packages/core/upload/admin/src/components/AssetCard/AudioPreview.js new file mode 100644 index 0000000000..96107bbcee --- /dev/null +++ b/packages/core/upload/admin/src/components/AssetCard/AudioPreview.js @@ -0,0 +1,24 @@ +/* eslint-disable jsx-a11y/media-has-caption */ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Box } from '@strapi/design-system/Box'; +import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden'; + +export const AudioPreview = ({ url, mime, alt }) => { + return ( + + + {alt} + + ); +}; + +AudioPreview.defaultProps = {}; + +AudioPreview.propTypes = { + alt: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + mime: PropTypes.string.isRequired, +}; diff --git a/packages/core/upload/admin/src/components/AssetCard/UploadingAssetCard.js b/packages/core/upload/admin/src/components/AssetCard/UploadingAssetCard.js index bc9fbfc5fd..169cb6a088 100644 --- a/packages/core/upload/admin/src/components/AssetCard/UploadingAssetCard.js +++ b/packages/core/upload/admin/src/components/AssetCard/UploadingAssetCard.js @@ -44,6 +44,11 @@ export const UploadingAssetCard = ({ asset, onCancel, onStatusChange, addUploade id: getTrad('settings.section.video.label'), defaultMessage: 'Video', }); + } else if (asset.type === AssetType.Audio) { + badgeContent = formatMessage({ + id: getTrad('settings.section.audio.label'), + defaultMessage: 'Audio', + }); } else { badgeContent = formatMessage({ id: getTrad('settings.section.doc.label'), diff --git a/packages/core/upload/admin/src/components/AssetList/index.js b/packages/core/upload/admin/src/components/AssetList/index.js index 7bcb90f612..aede72d11b 100644 --- a/packages/core/upload/admin/src/components/AssetList/index.js +++ b/packages/core/upload/admin/src/components/AssetList/index.js @@ -75,7 +75,7 @@ export const AssetList = ({ }; AssetList.defaultProps = { - allowedTypes: ['images', 'files', 'videos'], + allowedTypes: ['images', 'files', 'videos', 'audios'], onEditAsset: undefined, size: 'M', onReorderAsset: undefined, diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/AssetPreview.js b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/AssetPreview.js index 676d8cf7b6..ebcc4a3b89 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/AssetPreview.js +++ b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/AssetPreview.js @@ -27,6 +27,14 @@ export const AssetPreview = forwardRef(({ mime, url, name }, ref) => { ); } + if (mime.includes(AssetType.Audio)) { + return ( + + ); + } + if (mime.includes('pdf')) { return ( diff --git a/packages/core/upload/admin/src/components/MediaLibraryDialog/index.js b/packages/core/upload/admin/src/components/MediaLibraryDialog/index.js index 90540ad565..ed4aa58d82 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryDialog/index.js +++ b/packages/core/upload/admin/src/components/MediaLibraryDialog/index.js @@ -27,7 +27,7 @@ export const MediaLibraryDialog = ({ onClose, onSelectAssets, allowedTypes }) => }; MediaLibraryDialog.defaultProps = { - allowedTypes: ['files', 'images', 'videos'], + allowedTypes: ['files', 'images', 'videos', 'audios'], }; MediaLibraryDialog.propTypes = { diff --git a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js index b11b0dfc7b..4dfce2ce06 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js +++ b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAsset.js @@ -6,6 +6,7 @@ import { Box } from '@strapi/design-system/Box'; import { Flex } from '@strapi/design-system/Flex'; import { AssetType, AssetDefinition } from '../../../constants'; import { VideoPreview } from '../../AssetCard/VideoPreview'; +import { AudioPreview } from '../../AssetCard/AudioPreview'; import { createAssetUrl } from '../../../utils/createAssetUrl'; const DocAsset = styled(Flex)` @@ -33,6 +34,16 @@ export const CarouselAsset = ({ asset }) => { ); } + if (asset.mime.includes(AssetType.Audio)) { + return ( + + ); + } + if (asset.mime.includes(AssetType.Image)) { return ( { - const fieldAllowedTypes = allowedTypes || ['files', 'images', 'videos']; + const fieldAllowedTypes = allowedTypes || ['files', 'images', 'videos', 'audios']; const [uploadedFiles, setUploadedFiles] = useState([]); const [step, setStep] = useState(undefined); const [selectedIndex, setSelectedIndex] = useState(0); @@ -180,7 +180,7 @@ export const MediaLibraryInput = ({ }; MediaLibraryInput.defaultProps = { - attribute: { allowedTypes: ['videos', 'files', 'images'] }, + attribute: { allowedTypes: ['videos', 'files', 'images', 'audios'] }, disabled: false, description: undefined, error: undefined, diff --git a/packages/core/upload/admin/src/constants.js b/packages/core/upload/admin/src/constants.js index def61b74ac..0f2d9b54da 100644 --- a/packages/core/upload/admin/src/constants.js +++ b/packages/core/upload/admin/src/constants.js @@ -4,6 +4,7 @@ export const AssetType = { Video: 'video', Image: 'image', Document: 'doc', + Audio: 'audio', }; export const AssetSource = { diff --git a/packages/core/upload/admin/src/utils/typeFromMime.js b/packages/core/upload/admin/src/utils/typeFromMime.js index 700ff34675..ac569314ad 100644 --- a/packages/core/upload/admin/src/utils/typeFromMime.js +++ b/packages/core/upload/admin/src/utils/typeFromMime.js @@ -7,6 +7,9 @@ export const typeFromMime = mime => { if (mime.includes(AssetType.Video)) { return AssetType.Video; } + if (mime.includes(AssetType.Audio)) { + return AssetType.Audio; + } return AssetType.Document; }; diff --git a/packages/generators/generators/lib/plops/content-type.js b/packages/generators/generators/lib/plops/content-type.js index 65e7ce872f..bf5cc03c76 100644 --- a/packages/generators/generators/lib/plops/content-type.js +++ b/packages/generators/generators/lib/plops/content-type.js @@ -73,7 +73,7 @@ module.exports = plop => { } if (answer.attributeType === 'media') { - val.allowedTypes = ['images', 'files', 'videos']; + val.allowedTypes = ['images', 'files', 'videos', 'audios']; val.multiple = answer.multiple; }