diff --git a/packages/core/upload/admin/custom.d.ts b/packages/core/upload/admin/custom.d.ts new file mode 100644 index 0000000000..26db8f3629 --- /dev/null +++ b/packages/core/upload/admin/custom.d.ts @@ -0,0 +1,9 @@ +export {}; + +declare global { + interface Window { + strapi: { + backendURL: string; + }; + } +} diff --git a/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/Filters.jsx b/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/Filters.jsx index dcbc943637..56e1a745af 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/Filters.jsx +++ b/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/Filters.jsx @@ -5,7 +5,7 @@ import { Filter } from '@strapi/icons'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import displayedFilters from '../../../utils/displayedFilters'; +import { displayedFilters } from '../../../utils'; import FilterList from '../../FilterList'; import FilterPopover from '../../FilterPopover'; diff --git a/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/SearchAsset/index.jsx b/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/SearchAsset/index.jsx index 38cc21083d..6920143393 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/SearchAsset/index.jsx +++ b/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/SearchAsset/index.jsx @@ -6,7 +6,7 @@ import { Search } from '@strapi/icons'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import getTrad from '../../../../utils/getTrad'; +import { getTrad } from '../../../../utils'; const SearchAsset = ({ onChangeSearch, queryValue }) => { const { formatMessage } = useIntl(); diff --git a/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/index.jsx b/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/index.jsx index 54b67e1129..7e80ac33db 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/index.jsx +++ b/packages/core/upload/admin/src/components/AssetDialog/BrowseStep/index.jsx @@ -24,9 +24,7 @@ import { } from '../../../constants'; import { useFolder } from '../../../hooks/useFolder'; import { usePersistentState } from '../../../hooks/usePersistentState'; -import { getBreadcrumbDataCM, toSingularTypes } from '../../../utils'; -import getAllowedFiles from '../../../utils/getAllowedFiles'; -import getTrad from '../../../utils/getTrad'; +import { getBreadcrumbDataCM, toSingularTypes, getTrad, getAllowedFiles } from '../../../utils'; import { AssetGridList } from '../../AssetGridList'; import { Breadcrumbs } from '../../Breadcrumbs'; import { EmptyAssets } from '../../EmptyAssets'; diff --git a/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.jsx b/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.jsx index 3aa36c9b6a..b73fad5fab 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.jsx +++ b/packages/core/upload/admin/src/components/AssetDialog/SelectedStep/index.jsx @@ -4,7 +4,7 @@ import { Flex, Typography } from '@strapi/design-system'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils'; import { AssetGridList } from '../../AssetGridList'; export const SelectedStep = ({ selectedAssets, onSelectAsset, onReorderAsset }) => { diff --git a/packages/core/upload/admin/src/components/AssetDialog/index.jsx b/packages/core/upload/admin/src/components/AssetDialog/index.jsx index 5f74968066..5d9625cb7d 100644 --- a/packages/core/upload/admin/src/components/AssetDialog/index.jsx +++ b/packages/core/upload/admin/src/components/AssetDialog/index.jsx @@ -12,9 +12,7 @@ import { useFolders } from '../../hooks/useFolders'; import { useMediaLibraryPermissions } from '../../hooks/useMediaLibraryPermissions'; import useModalQueryParams from '../../hooks/useModalQueryParams'; import { useSelectionState } from '../../hooks/useSelectionState'; -import { containsAssetFilter, getTrad } from '../../utils'; -import getAllowedFiles from '../../utils/getAllowedFiles'; -import { moveElement } from '../../utils/moveElement'; +import { containsAssetFilter, getTrad, getAllowedFiles, moveElement } from '../../utils'; import { EditAssetContent } from '../EditAssetDialog'; import { EditFolderContent } from '../EditFolderDialog'; diff --git a/packages/core/upload/admin/src/components/BulkMoveDialog/BulkMoveDialog.jsx b/packages/core/upload/admin/src/components/BulkMoveDialog/BulkMoveDialog.jsx index 3cc3ed6183..d44acaca7e 100644 --- a/packages/core/upload/admin/src/components/BulkMoveDialog/BulkMoveDialog.jsx +++ b/packages/core/upload/admin/src/components/BulkMoveDialog/BulkMoveDialog.jsx @@ -9,8 +9,7 @@ import { useIntl } from 'react-intl'; import { AssetDefinition, FolderDefinition } from '../../constants'; import { useBulkMove } from '../../hooks/useBulkMove'; import { useFolderStructure } from '../../hooks/useFolderStructure'; -import { getTrad } from '../../utils'; -import { normalizeAPIError } from '../../utils/normalizeAPIError'; +import { getTrad, normalizeAPIError } from '../../utils'; import SelectTree from '../SelectTree'; export const BulkMoveDialog = ({ onClose, selected, currentFolder }) => { diff --git a/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx b/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx index 0aa907c87b..bbeefb820d 100644 --- a/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx +++ b/packages/core/upload/admin/src/components/CopyLinkButton/index.jsx @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { useClipboard } from '../../hooks/useClipboard'; -import getTrad from '../../utils/getTrad'; +import { getTrad } from '../../utils'; export const CopyLinkButton = ({ url }) => { const { toggleNotification } = useNotification(); diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/CroppingActions.jsx b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/CroppingActions.jsx index b0cf5bb27e..2d3eb9db1d 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/CroppingActions.jsx +++ b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/CroppingActions.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { styled, useTheme } from 'styled-components'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils'; import { CroppingActionRow } from './components'; diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.jsx b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.jsx index edbbc81079..61b760dfa2 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.jsx +++ b/packages/core/upload/admin/src/components/EditAssetDialog/PreviewBox/index.jsx @@ -10,9 +10,7 @@ import { AssetDefinition, AssetType } from '../../../constants'; import { useCropImg } from '../../../hooks/useCropImg'; import { useEditAsset } from '../../../hooks/useEditAsset'; import { useUpload } from '../../../hooks/useUpload'; -import { createAssetUrl } from '../../../utils'; -import { downloadFile } from '../../../utils/downloadFile'; -import getTrad from '../../../utils/getTrad'; +import { createAssetUrl, getTrad, downloadFile } from '../../../utils'; import { CopyLinkButton } from '../../CopyLinkButton'; import { UploadProgress } from '../../UploadProgress'; import { RemoveAssetDialog } from '../RemoveAssetDialog'; diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/index.jsx b/packages/core/upload/admin/src/components/EditAssetDialog/index.jsx index 54167f7819..41ff32ea37 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/index.jsx +++ b/packages/core/upload/admin/src/components/EditAssetDialog/index.jsx @@ -27,8 +27,7 @@ import * as yup from 'yup'; import { AssetDefinition } from '../../constants'; import { useEditAsset } from '../../hooks/useEditAsset'; import { useFolderStructure } from '../../hooks/useFolderStructure'; -import { findRecursiveFolderByValue, getTrad, getFileExtension } from '../../utils'; -import formatBytes from '../../utils/formatBytes'; +import { findRecursiveFolderByValue, getTrad, getFileExtension, formatBytes } from '../../utils'; import { ContextInfo } from '../ContextInfo'; import SelectTree from '../SelectTree'; diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/tests/EditAssetDialog.test.jsx b/packages/core/upload/admin/src/components/EditAssetDialog/tests/EditAssetDialog.test.jsx index af39e4abd9..cad8a0ff1d 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/tests/EditAssetDialog.test.jsx +++ b/packages/core/upload/admin/src/components/EditAssetDialog/tests/EditAssetDialog.test.jsx @@ -7,7 +7,7 @@ import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from 'react-query'; import en from '../../../translations/en.json'; -import { downloadFile } from '../../../utils/downloadFile'; +import { downloadFile } from '../../../utils'; import { EditAssetDialog } from '../index'; jest.mock('../../../hooks/useFolderStructure'); diff --git a/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.jsx b/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.jsx index 43f63c87e4..b7a04ea905 100644 --- a/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.jsx +++ b/packages/core/upload/admin/src/components/EditAssetDialog/tests/index.test.jsx @@ -13,7 +13,7 @@ import { IntlProvider } from 'react-intl'; import { QueryClient, QueryClientProvider } from 'react-query'; import en from '../../../translations/en.json'; -import { downloadFile } from '../../../utils/downloadFile'; +import { downloadFile } from '../../../utils'; import { EditAssetDialog } from '../index'; jest.mock('../../../utils/downloadFile'); diff --git a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssetActions.jsx b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssetActions.jsx index a148912b2f..e1d9ddb9a1 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssetActions.jsx +++ b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssetActions.jsx @@ -6,8 +6,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { AssetDefinition } from '../../../constants'; -import { prefixFileUrlWithBackendUrl } from '../../../utils'; -import getTrad from '../../../utils/getTrad'; +import { getTrad, prefixFileUrlWithBackendUrl } from '../../../utils'; import { CopyLinkButton } from '../../CopyLinkButton'; export const CarouselAssetActions = ({ asset, onDeleteAsset, onAddAsset, onEditAsset }) => { diff --git a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssets.jsx b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssets.jsx index 617b1c0367..7c64d0a5dc 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssets.jsx +++ b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/CarouselAssets.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { AssetDefinition } from '../../../constants'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils/getTrad'; import { EditAssetDialog } from '../../EditAssetDialog'; import { CarouselAsset } from './CarouselAsset'; diff --git a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/EmptyStateAsset.jsx b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/EmptyStateAsset.jsx index a942a83dcc..dc762b56f6 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/EmptyStateAsset.jsx +++ b/packages/core/upload/admin/src/components/MediaLibraryInput/Carousel/EmptyStateAsset.jsx @@ -7,8 +7,7 @@ import { useIntl } from 'react-intl'; import { styled } from 'styled-components'; import { AssetSource } from '../../../constants'; -import getTrad from '../../../utils/getTrad'; -import { rawFileToAsset } from '../../../utils/rawFileToAsset'; +import { getTrad, rawFileToAsset } from '../../../utils'; const TextAlignTypography = styled(Typography)` align-items: center; diff --git a/packages/core/upload/admin/src/components/MediaLibraryInput/index.jsx b/packages/core/upload/admin/src/components/MediaLibraryInput/index.jsx index 07f779a509..18c2de2cfd 100644 --- a/packages/core/upload/admin/src/components/MediaLibraryInput/index.jsx +++ b/packages/core/upload/admin/src/components/MediaLibraryInput/index.jsx @@ -4,8 +4,7 @@ import { useField, useNotification } from '@strapi/admin/strapi-admin'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import getAllowedFiles from '../../utils/getAllowedFiles'; -import getTrad from '../../utils/getTrad'; +import { getTrad, getAllowedFiles } from '../../utils'; import { AssetDialog } from '../AssetDialog'; import { EditFolderDialog } from '../EditFolderDialog'; import { UploadAssetDialog } from '../UploadAssetDialog/UploadAssetDialog'; diff --git a/packages/core/upload/admin/src/components/SelectTree/utils/flattenTree.js b/packages/core/upload/admin/src/components/SelectTree/utils/flattenTree.js deleted file mode 100644 index fed8c80990..0000000000 --- a/packages/core/upload/admin/src/components/SelectTree/utils/flattenTree.js +++ /dev/null @@ -1,7 +0,0 @@ -export default function flattenTree(tree, parent, depth = 0) { - return tree.flatMap((item) => - item.children - ? [{ ...item, parent: parent?.value, depth }, ...flattenTree(item.children, item, depth + 1)] - : { ...item, depth, parent: parent?.value } - ); -} diff --git a/packages/core/upload/admin/src/components/SelectTree/utils/flattenTree.ts b/packages/core/upload/admin/src/components/SelectTree/utils/flattenTree.ts new file mode 100644 index 0000000000..ec6b88c5b1 --- /dev/null +++ b/packages/core/upload/admin/src/components/SelectTree/utils/flattenTree.ts @@ -0,0 +1,26 @@ +type TreeNode = { + value: T; + children?: TreeNode[]; + label?: string; +}; + +export type FlattenedNode = { + value: T; + parent?: T; + depth: number; + // we need the label in places where flattenTree is used + label?: string; + children?: TreeNode[]; +}; + +export default function flattenTree( + tree: TreeNode[], + parent: TreeNode | null = null, + depth: number = 0 +): FlattenedNode[] { + return tree.flatMap((item) => + item.children + ? [{ ...item, parent: parent?.value, depth }, ...flattenTree(item.children, item, depth + 1)] + : { ...item, depth, parent: parent?.value } + ); +} diff --git a/packages/core/upload/admin/src/components/SelectTree/utils/tests/__snapshots__/flattenTree.test.js.snap b/packages/core/upload/admin/src/components/SelectTree/utils/tests/__snapshots__/flattenTree.test.ts.snap similarity index 100% rename from packages/core/upload/admin/src/components/SelectTree/utils/tests/__snapshots__/flattenTree.test.js.snap rename to packages/core/upload/admin/src/components/SelectTree/utils/tests/__snapshots__/flattenTree.test.ts.snap diff --git a/packages/core/upload/admin/src/components/SelectTree/utils/tests/flattenTree.test.js b/packages/core/upload/admin/src/components/SelectTree/utils/tests/flattenTree.test.ts similarity index 100% rename from packages/core/upload/admin/src/components/SelectTree/utils/tests/flattenTree.test.js rename to packages/core/upload/admin/src/components/SelectTree/utils/tests/flattenTree.test.ts diff --git a/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/AddAssetStep.jsx b/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/AddAssetStep.jsx index 2d1a73e6db..d541f322a6 100644 --- a/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/AddAssetStep.jsx +++ b/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/AddAssetStep.jsx @@ -4,7 +4,7 @@ import { Box, Divider, Modal, Tabs } from '@strapi/design-system'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils'; import { FromComputerForm } from './FromComputerForm'; import { FromUrlForm } from './FromUrlForm'; diff --git a/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromComputerForm.jsx b/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromComputerForm.jsx index b57c8ee859..b8781472f7 100644 --- a/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromComputerForm.jsx +++ b/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromComputerForm.jsx @@ -9,8 +9,7 @@ import { useIntl } from 'react-intl'; import { styled } from 'styled-components'; import { AssetSource } from '../../../constants'; -import getTrad from '../../../utils/getTrad'; -import { rawFileToAsset } from '../../../utils/rawFileToAsset'; +import { getTrad, rawFileToAsset } from '../../../utils'; const Wrapper = styled(Flex)` flex-direction: column; diff --git a/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromUrlForm.jsx b/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromUrlForm.jsx index cc08484dd8..6655b82680 100644 --- a/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromUrlForm.jsx +++ b/packages/core/upload/admin/src/components/UploadAssetDialog/AddAssetStep/FromUrlForm.jsx @@ -6,9 +6,7 @@ import { Form, Formik } from 'formik'; import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; -import getTrad from '../../../utils/getTrad'; -import { urlsToAssets } from '../../../utils/urlsToAssets'; -import { urlSchema } from '../../../utils/urlYupSchema'; +import { getTrad, urlsToAssets, urlSchema } from '../../../utils'; export const FromUrlForm = ({ onClose, onAddAsset, trackedLocation }) => { const [loading, setLoading] = useState(false); diff --git a/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.jsx b/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.jsx index 723655a2ec..60aee8fe3b 100644 --- a/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.jsx +++ b/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.jsx @@ -6,7 +6,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { AssetDefinition } from '../../../constants'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils'; import { AssetCard } from '../../AssetCard/AssetCard'; import { UploadingAssetCard } from '../../AssetCard/UploadingAssetCard'; diff --git a/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/tests/PendingAssetStep.test.jsx b/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/tests/PendingAssetStep.test.jsx index 2c9c3fe105..767668a6fe 100644 --- a/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/tests/PendingAssetStep.test.jsx +++ b/packages/core/upload/admin/src/components/UploadAssetDialog/PendingAssetStep/tests/PendingAssetStep.test.jsx @@ -7,7 +7,10 @@ import { QueryClient, QueryClientProvider } from 'react-query'; import { PendingAssetStep } from '../PendingAssetStep'; -jest.mock('../../../../utils/getTrad', () => (x) => x); +jest.mock('../../../../utils', () => ({ + ...jest.requireActual('../../../../utils'), + getTrad: (x) => x, +})); const queryClient = new QueryClient({ defaultOptions: { diff --git a/packages/core/upload/admin/src/index.js b/packages/core/upload/admin/src/index.js index 398fe5678c..acee5f9d4a 100644 --- a/packages/core/upload/admin/src/index.js +++ b/packages/core/upload/admin/src/index.js @@ -6,8 +6,7 @@ import { MediaLibraryDialog } from './components/MediaLibraryDialog'; import { MediaLibraryInput } from './components/MediaLibraryInput'; import { PERMISSIONS } from './constants'; import pluginId from './pluginId'; -import getTrad from './utils/getTrad'; -import { prefixPluginTranslations } from './utils/prefixPluginTranslations'; +import { getTrad, prefixPluginTranslations } from './utils'; const name = pluginPkg.strapi.name; diff --git a/packages/core/upload/admin/src/newConstants.ts b/packages/core/upload/admin/src/newConstants.ts new file mode 100644 index 0000000000..40fd1161f6 --- /dev/null +++ b/packages/core/upload/admin/src/newConstants.ts @@ -0,0 +1,12 @@ +// TODO: replace this file with the constants file when it will be migrated to TS +export enum AssetType { + Video = 'video', + Image = 'image', + Document = 'doc', + Audio = 'audio', +} + +export enum AssetSource { + Url = 'url', + Computer = 'computer', +} diff --git a/packages/core/upload/admin/src/pages/App/ConfigureTheView/components/Settings.jsx b/packages/core/upload/admin/src/pages/App/ConfigureTheView/components/Settings.jsx index 285dbd2069..fac65f2ef7 100644 --- a/packages/core/upload/admin/src/pages/App/ConfigureTheView/components/Settings.jsx +++ b/packages/core/upload/admin/src/pages/App/ConfigureTheView/components/Settings.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { pageSizes, sortOptions } from '../../../../constants'; -import getTrad from '../../../../utils/getTrad'; +import { getTrad } from '../../../../utils'; const Settings = ({ sort = '', pageSize = 10, onChange }) => { const { formatMessage } = useIntl(); diff --git a/packages/core/upload/admin/src/pages/App/ConfigureTheView/index.jsx b/packages/core/upload/admin/src/pages/App/ConfigureTheView/index.jsx index 7e1a539abd..0b22bd4d61 100644 --- a/packages/core/upload/admin/src/pages/App/ConfigureTheView/index.jsx +++ b/packages/core/upload/admin/src/pages/App/ConfigureTheView/index.jsx @@ -16,7 +16,7 @@ import { NavLink } from 'react-router-dom'; import { useConfig } from '../../../hooks/useConfig'; import pluginID from '../../../pluginId'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils'; import { Settings } from './components/Settings'; import { onChange, setLoaded } from './state/actions'; diff --git a/packages/core/upload/admin/src/pages/App/MediaLibrary/components/BulkActions.jsx b/packages/core/upload/admin/src/pages/App/MediaLibrary/components/BulkActions.jsx index 5eab0ce455..e55e7cd2e1 100644 --- a/packages/core/upload/admin/src/pages/App/MediaLibrary/components/BulkActions.jsx +++ b/packages/core/upload/admin/src/pages/App/MediaLibrary/components/BulkActions.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { AssetDefinition, FolderDefinition } from '../../../../constants'; -import getTrad from '../../../../utils/getTrad'; +import { getTrad } from '../../../../utils'; import { BulkDeleteButton } from './BulkDeleteButton'; import { BulkMoveButton } from './BulkMoveButton'; diff --git a/packages/core/upload/admin/src/pages/App/MediaLibrary/components/Filters.jsx b/packages/core/upload/admin/src/pages/App/MediaLibrary/components/Filters.jsx index 2553318d7e..cf352a41c2 100644 --- a/packages/core/upload/admin/src/pages/App/MediaLibrary/components/Filters.jsx +++ b/packages/core/upload/admin/src/pages/App/MediaLibrary/components/Filters.jsx @@ -7,7 +7,7 @@ import { useIntl } from 'react-intl'; import FilterList from '../../../../components/FilterList'; import FilterPopover from '../../../../components/FilterPopover'; -import displayedFilters from '../../../../utils/displayedFilters'; +import { displayedFilters } from '../../../../utils'; export const Filters = () => { const [open, setOpen] = React.useState(false); diff --git a/packages/core/upload/admin/src/pages/App/components/BulkActions.jsx b/packages/core/upload/admin/src/pages/App/components/BulkActions.jsx index adc8633c17..3d282b7617 100644 --- a/packages/core/upload/admin/src/pages/App/components/BulkActions.jsx +++ b/packages/core/upload/admin/src/pages/App/components/BulkActions.jsx @@ -5,7 +5,7 @@ import PropTypes from 'prop-types'; import { useIntl } from 'react-intl'; import { AssetDefinition, FolderDefinition } from '../../../constants'; -import getTrad from '../../../utils/getTrad'; +import { getTrad } from '../../../utils'; import { BulkDeleteButton } from './BulkDeleteButton'; import { BulkMoveButton } from './BulkMoveButton'; diff --git a/packages/core/upload/admin/src/pages/App/components/Filters.jsx b/packages/core/upload/admin/src/pages/App/components/Filters.jsx index dac1fdd2f2..a195602ee8 100644 --- a/packages/core/upload/admin/src/pages/App/components/Filters.jsx +++ b/packages/core/upload/admin/src/pages/App/components/Filters.jsx @@ -7,7 +7,7 @@ import { useIntl } from 'react-intl'; import FilterList from '../../../components/FilterList'; import FilterPopover from '../../../components/FilterPopover'; -import displayedFilters from '../../../utils/displayedFilters'; +import { displayedFilters } from '../../../utils'; export const Filters = () => { const [open, setOpen] = React.useState(false); diff --git a/packages/core/upload/admin/src/pluginId.js b/packages/core/upload/admin/src/pluginId.ts similarity index 100% rename from packages/core/upload/admin/src/pluginId.js rename to packages/core/upload/admin/src/pluginId.ts diff --git a/packages/core/upload/admin/src/utils/appendSearchParamsToUrl.js b/packages/core/upload/admin/src/utils/appendSearchParamsToUrl.js deleted file mode 100644 index 8c66447171..0000000000 --- a/packages/core/upload/admin/src/utils/appendSearchParamsToUrl.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Append the given search params to the given URL. - * - * @param {String} url The URL string to append the search params to - * @param {Object} params The object of search params to append to the URL - * @returns {String} A string representing the URL with the search params appended - */ -const appendSearchParamsToUrl = ({ url, params }) => { - if (url === undefined || typeof params !== 'object') { - return url; - } - - const urlObj = new URL(url, window.strapi.backendURL); - - Object.entries(params).forEach(([key, value]) => { - if (value !== undefined) { - urlObj.searchParams.append(key, value); - } - }); - - return urlObj.toString(); -}; - -export { appendSearchParamsToUrl }; diff --git a/packages/core/upload/admin/src/utils/appendSearchParamsToUrl.ts b/packages/core/upload/admin/src/utils/appendSearchParamsToUrl.ts new file mode 100644 index 0000000000..b859c1295e --- /dev/null +++ b/packages/core/upload/admin/src/utils/appendSearchParamsToUrl.ts @@ -0,0 +1,22 @@ +interface AppendSearchParamsToUrlProps { + url?: string; + params?: Record | string; +} + +const appendSearchParamsToUrl = ({ url, params }: AppendSearchParamsToUrlProps) => { + if (url === undefined || typeof params !== 'object') { + return url; + } + + const urlObj = new URL(url, window.strapi.backendURL); + + Object.entries(params).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + urlObj.searchParams.append(key, value); + } + }); + + return urlObj.toString(); +}; + +export { appendSearchParamsToUrl }; diff --git a/packages/core/upload/admin/src/utils/containsAssetFilter.js b/packages/core/upload/admin/src/utils/containsAssetFilter.ts similarity index 57% rename from packages/core/upload/admin/src/utils/containsAssetFilter.js rename to packages/core/upload/admin/src/utils/containsAssetFilter.ts index cbb7d6b29f..f6ab2321b7 100644 --- a/packages/core/upload/admin/src/utils/containsAssetFilter.js +++ b/packages/core/upload/admin/src/utils/containsAssetFilter.ts @@ -1,4 +1,6 @@ -const containsMimeTypeFilter = (query) => { +import type { Query } from '../../../shared/contracts/files'; + +const containsMimeTypeFilter = (query: Query | null) => { const filters = query?.filters?.$and; if (!filters) { @@ -12,8 +14,6 @@ const containsMimeTypeFilter = (query) => { return !!result; }; -const containsAssetFilter = (query) => { +export const containsAssetFilter = (query: Query | null) => { return containsMimeTypeFilter(query); }; - -export default containsAssetFilter; diff --git a/packages/core/upload/admin/src/utils/createAssetUrl.js b/packages/core/upload/admin/src/utils/createAssetUrl.js deleted file mode 100644 index a70ff33ee3..0000000000 --- a/packages/core/upload/admin/src/utils/createAssetUrl.js +++ /dev/null @@ -1,20 +0,0 @@ -import prefixFileUrlWithBackendUrl from './prefixFileUrlWithBackendUrl'; - -/** - * Create image URL for asset - * @param {Object} asset - * @param {Boolean} forThumbnail - if true, return URL for thumbnail - * if there's no thumbnail, return the URL of the original image. - * @return {String} URL - */ -const createAssetUrl = (asset, forThumbnail = true) => { - if (asset.isLocal) { - return asset.url; - } - - const assetUrl = forThumbnail ? asset?.formats?.thumbnail?.url || asset.url : asset.url; - - return prefixFileUrlWithBackendUrl(assetUrl); -}; - -export default createAssetUrl; diff --git a/packages/core/upload/admin/src/utils/createAssetUrl.ts b/packages/core/upload/admin/src/utils/createAssetUrl.ts new file mode 100644 index 0000000000..240e817c24 --- /dev/null +++ b/packages/core/upload/admin/src/utils/createAssetUrl.ts @@ -0,0 +1,13 @@ +import { prefixFileUrlWithBackendUrl } from './prefixFileUrlWithBackendUrl'; + +import type { File } from '../../../shared/contracts/files'; + +export const createAssetUrl = (asset: File, forThumbnail = true) => { + if (asset.isLocal) { + return asset.url; + } + + const assetUrl = forThumbnail ? asset?.formats?.thumbnail?.url || asset.url : asset.url; + + return prefixFileUrlWithBackendUrl(assetUrl); +}; diff --git a/packages/core/upload/admin/src/utils/displayedFilters.js b/packages/core/upload/admin/src/utils/displayedFilters.ts similarity index 90% rename from packages/core/upload/admin/src/utils/displayedFilters.js rename to packages/core/upload/admin/src/utils/displayedFilters.ts index e2b7e02cb8..2f02e77edb 100644 --- a/packages/core/upload/admin/src/utils/displayedFilters.js +++ b/packages/core/upload/admin/src/utils/displayedFilters.ts @@ -1,4 +1,4 @@ -const displayedFilters = [ +export const displayedFilters = [ { name: 'createdAt', fieldSchema: { @@ -27,5 +27,3 @@ const displayedFilters = [ metadatas: { label: 'type' }, }, ]; - -export default displayedFilters; diff --git a/packages/core/upload/admin/src/utils/downloadFile.js b/packages/core/upload/admin/src/utils/downloadFile.ts similarity index 78% rename from packages/core/upload/admin/src/utils/downloadFile.js rename to packages/core/upload/admin/src/utils/downloadFile.ts index 799c87f805..4177fcf2c2 100644 --- a/packages/core/upload/admin/src/utils/downloadFile.js +++ b/packages/core/upload/admin/src/utils/downloadFile.ts @@ -1,4 +1,4 @@ -export const downloadFile = async (url, fileName) => { +export const downloadFile = async (url: string, fileName: string) => { const fileBlob = await fetch(url).then((res) => res.blob()); const urlDownload = window.URL.createObjectURL(fileBlob); const link = document.createElement('a'); diff --git a/packages/core/upload/admin/src/utils/findRecursiveFolderByValue.js b/packages/core/upload/admin/src/utils/findRecursiveFolderByValue.js deleted file mode 100644 index 44769bcd8d..0000000000 --- a/packages/core/upload/admin/src/utils/findRecursiveFolderByValue.js +++ /dev/null @@ -1,17 +0,0 @@ -export default function findRecursiveFolderByValue(data, value) { - let result; - - function iter(a) { - if (a.value === value) { - result = a; - - return true; - } - - return Array.isArray(a.children) && a.children.some(iter); - } - - data.some(iter); - - return result; -} diff --git a/packages/core/upload/admin/src/utils/findRecursiveFolderByValue.ts b/packages/core/upload/admin/src/utils/findRecursiveFolderByValue.ts new file mode 100644 index 0000000000..7a6073b866 --- /dev/null +++ b/packages/core/upload/admin/src/utils/findRecursiveFolderByValue.ts @@ -0,0 +1,26 @@ +import type { FolderNode } from '../../../shared/contracts/folders'; + +interface FolderStructureValue extends Omit { + value: number | null; + children?: FolderStructureValue[]; +} + +type Value = number | null | { value: number | null }; + +export function findRecursiveFolderByValue(data: FolderStructureValue[], value: Value) { + let result; + + function iter(a: FolderStructureValue) { + if (a.value === value) { + result = a; + + return true; + } + + return Array.isArray(a.children) && a.children.some(iter); + } + + data.some(iter); + + return result; +} diff --git a/packages/core/upload/admin/src/utils/findRecursiveFolderMetadatas.js b/packages/core/upload/admin/src/utils/findRecursiveFolderMetadatas.js deleted file mode 100644 index 84991041ce..0000000000 --- a/packages/core/upload/admin/src/utils/findRecursiveFolderMetadatas.js +++ /dev/null @@ -1,15 +0,0 @@ -const findRecursiveFolderMetadatas = (node, searchedId, parentId = null) => { - let result = null; - - if (node.value === parseInt(searchedId, 10)) { - result = { parentId, currentFolderLabel: node.label }; - } else { - for (let i = 0; i < node.children.length && !result; i++) { - result = findRecursiveFolderMetadatas(node.children[i], searchedId, node.value); - } - } - - return result; -}; - -export default findRecursiveFolderMetadatas; diff --git a/packages/core/upload/admin/src/utils/formatBytes.js b/packages/core/upload/admin/src/utils/formatBytes.js deleted file mode 100644 index cc3ba0c158..0000000000 --- a/packages/core/upload/admin/src/utils/formatBytes.js +++ /dev/null @@ -1,13 +0,0 @@ -import byteSize from 'byte-size'; - -function formatBytes(receivedBytes, decimals = 0) { - const { value, unit } = byteSize(receivedBytes * 1000, { precision: decimals }); - - if (!unit) { - return '0B'; - } - - return `${value}${unit.toUpperCase()}`; -} - -export default formatBytes; diff --git a/packages/core/upload/admin/src/utils/formatBytes.ts b/packages/core/upload/admin/src/utils/formatBytes.ts new file mode 100644 index 0000000000..74d7c16e5a --- /dev/null +++ b/packages/core/upload/admin/src/utils/formatBytes.ts @@ -0,0 +1,12 @@ +import byteSize from 'byte-size'; + +export function formatBytes(receivedBytes: number | string, decimals = 0) { + const realBytes = typeof receivedBytes === 'string' ? Number(receivedBytes) : receivedBytes; + const { value, unit } = byteSize(realBytes * 1000, { precision: decimals }); + + if (!unit) { + return '0B'; + } + + return `${value}${unit.toUpperCase()}`; +} diff --git a/packages/core/upload/admin/src/utils/formatDuration.js b/packages/core/upload/admin/src/utils/formatDuration.ts similarity index 64% rename from packages/core/upload/admin/src/utils/formatDuration.js rename to packages/core/upload/admin/src/utils/formatDuration.ts index 97bbe26aa1..bc44cabbd4 100644 --- a/packages/core/upload/admin/src/utils/formatDuration.js +++ b/packages/core/upload/admin/src/utils/formatDuration.ts @@ -1,8 +1,8 @@ import { intervalToDuration } from 'date-fns'; -const zeroPad = (num) => String(num).padStart(2, '0'); +const zeroPad = (num?: number) => String(num).padStart(2, '0'); -export const formatDuration = (durationInSecond) => { +export const formatDuration = (durationInSecond: number) => { const duration = intervalToDuration({ start: 0, end: durationInSecond * 1000 }); return `${zeroPad(duration.hours)}:${zeroPad(duration.minutes)}:${zeroPad(duration.seconds)}`; diff --git a/packages/core/upload/admin/src/utils/getAPIInnerErrors.js b/packages/core/upload/admin/src/utils/getAPIInnerErrors.ts similarity index 55% rename from packages/core/upload/admin/src/utils/getAPIInnerErrors.js rename to packages/core/upload/admin/src/utils/getAPIInnerErrors.ts index 52f28d062b..5fda8f1fbd 100644 --- a/packages/core/upload/admin/src/utils/getAPIInnerErrors.js +++ b/packages/core/upload/admin/src/utils/getAPIInnerErrors.ts @@ -1,15 +1,24 @@ import { normalizeAPIError } from './normalizeAPIError'; +import type { FetchError } from '@strapi/admin/strapi-admin'; +import type { MessageDescriptor } from 'react-intl'; + +type GetAPIInnerErrorsReturn = { + [key: string]: MessageDescriptor; +}; /** * * Returns a normalized error message * */ -function getAPIInnerErrors(error, { getTrad }) { +export function getAPIInnerErrors( + error: FetchError, + { getTrad }: { getTrad: (key: string) => string } +) { const normalizedError = normalizeAPIError(error, getTrad); if (normalizedError && 'errors' in normalizedError) { - return normalizedError.errors.reduce((acc, error) => { + return normalizedError.errors.reduce((acc, error) => { if ('path' in error.values) { acc[error.values.path] = { id: error.id, @@ -23,5 +32,3 @@ function getAPIInnerErrors(error, { getTrad }) { return normalizedError?.defaultMessage; } - -export default getAPIInnerErrors; diff --git a/packages/core/upload/admin/src/utils/getAllowedFiles.js b/packages/core/upload/admin/src/utils/getAllowedFiles.ts similarity index 54% rename from packages/core/upload/admin/src/utils/getAllowedFiles.js rename to packages/core/upload/admin/src/utils/getAllowedFiles.ts index 957424db77..a58bcab64f 100644 --- a/packages/core/upload/admin/src/utils/getAllowedFiles.js +++ b/packages/core/upload/admin/src/utils/getAllowedFiles.ts @@ -1,16 +1,28 @@ -import toSingularTypes from './toSingularTypes'; - +import { toSingularTypes } from './toSingularTypes'; +import type { File } from '../../../shared/contracts/files'; /** * Returns the files that can be added to the media field * @param {Object[]} pluralTypes Array of string (allowedTypes) * @param {Object[]} files Array of files * @returns Object[] */ -const getAllowedFiles = (pluralTypes, files) => { + +interface AllowedFiles extends File { + documentId: string; + isSelectable: boolean; + locale: string | null; + type: string; +} + +export const getAllowedFiles = (pluralTypes: string[], files: AllowedFiles[]) => { const singularTypes = toSingularTypes(pluralTypes); const allowedFiles = files.filter((file) => { - const fileType = file.mime.split('/')[0]; + const fileType = file?.mime?.split('/')[0]; + + if (!fileType) { + return false; + } if (singularTypes.includes('file') && !['video', 'image', 'audio'].includes(fileType)) { return true; @@ -21,5 +33,3 @@ const getAllowedFiles = (pluralTypes, files) => { return allowedFiles; }; - -export default getAllowedFiles; diff --git a/packages/core/upload/admin/src/utils/getBreadcrumbDataCM.js b/packages/core/upload/admin/src/utils/getBreadcrumbDataCM.js deleted file mode 100644 index 7cea8d4317..0000000000 --- a/packages/core/upload/admin/src/utils/getBreadcrumbDataCM.js +++ /dev/null @@ -1,34 +0,0 @@ -import getTrad from './getTrad'; - -const getBreadcrumbDataML = (folder) => { - let data = [ - { - id: null, - label: { id: getTrad('plugin.name'), defaultMessage: 'Media Library' }, - }, - ]; - - if (folder?.parent?.parent) { - data.push([]); - } - - if (folder?.parent) { - data.push({ - id: folder.parent.id, - label: folder.parent.name, - path: folder.parent.path, - }); - } - - if (folder) { - data.push({ - id: folder.id, - label: folder.name, - path: folder.path, - }); - } - - return data; -}; - -export default getBreadcrumbDataML; diff --git a/packages/core/upload/admin/src/utils/getBreadcrumbDataCM.ts b/packages/core/upload/admin/src/utils/getBreadcrumbDataCM.ts new file mode 100644 index 0000000000..39ef0de846 --- /dev/null +++ b/packages/core/upload/admin/src/utils/getBreadcrumbDataCM.ts @@ -0,0 +1,53 @@ +import { getTrad } from './getTrad'; + +import type { Folder } from '../../../shared/contracts/folders'; +import type { MessageDescriptor } from 'react-intl'; + +interface BreadcrumbDataFolder extends Omit { + parent?: BreadcrumbDataFolder; + children?: { + count: number; + }; + files?: { + count: number; + }; +} + +interface BreadcrumbItem { + id?: number | null; + label?: MessageDescriptor | string; + path?: string; +} + +type BreadcrumbData = BreadcrumbItem | []; + +export const getBreadcrumbDataCM = (folder: BreadcrumbDataFolder | null) => { + let data: BreadcrumbData[] = [ + { + id: null, + label: { id: getTrad('plugin.name'), defaultMessage: 'Media Library' }, + }, + ]; + + if (folder?.parent?.parent) { + data.push([]); + } + + if (folder?.parent) { + data.push({ + id: folder.parent.id, + label: folder.parent.name, + path: folder.parent.path, + }); + } + + if (folder) { + data.push({ + id: folder.id, + label: folder.name, + path: folder.path, + }); + } + + return data; +}; diff --git a/packages/core/upload/admin/src/utils/getBreadcrumbDataML.js b/packages/core/upload/admin/src/utils/getBreadcrumbDataML.js deleted file mode 100644 index 05d4269294..0000000000 --- a/packages/core/upload/admin/src/utils/getBreadcrumbDataML.js +++ /dev/null @@ -1,38 +0,0 @@ -import getFolderURL from './getFolderURL'; -import getTrad from './getTrad'; - -const getBreadcrumbDataML = (folder, { pathname, query }) => { - let data = [ - { - id: null, - label: { id: getTrad('plugin.name'), defaultMessage: 'Media Library' }, - href: folder ? getFolderURL(pathname, query) : undefined, - }, - ]; - - if (folder?.parent?.parent) { - data.push([]); - } - - if (folder?.parent) { - data.push({ - id: folder.parent.id, - label: folder.parent.name, - href: getFolderURL(pathname, query, { - folder: folder.parent.id, - folderPath: folder.parent.path, - }), - }); - } - - if (folder) { - data.push({ - id: folder.id, - label: folder.name, - }); - } - - return data; -}; - -export default getBreadcrumbDataML; diff --git a/packages/core/upload/admin/src/utils/getBreadcrumbDataML.ts b/packages/core/upload/admin/src/utils/getBreadcrumbDataML.ts new file mode 100644 index 0000000000..f0b59dde02 --- /dev/null +++ b/packages/core/upload/admin/src/utils/getBreadcrumbDataML.ts @@ -0,0 +1,60 @@ +import { getFolderURL } from './getFolderURL'; +import { getTrad } from './getTrad'; +import type { Query } from '../../../shared/contracts/files'; +import type { Folder } from '../../../shared/contracts/folders'; +import type { MessageDescriptor } from 'react-intl'; + +interface GetBreadcrumbDataMLProps { + folder: Folder; + options: { + pathname: string; + query?: Query; + }; +} + +interface GetBreadcrumbDataMLReturn { + id: number | null; + label: string | MessageDescriptor; + href?: string; +} + +type BreadcrumbData = GetBreadcrumbDataMLReturn | []; + +export const getBreadcrumbDataML = ( + folder: GetBreadcrumbDataMLProps['folder'] | null, + { pathname, query }: GetBreadcrumbDataMLProps['options'] +) => { + let data: BreadcrumbData[] = [ + { + id: null, + label: { id: getTrad('plugin.name'), defaultMessage: 'Media Library' }, + href: folder ? getFolderURL(pathname, query || {}) : undefined, + }, + ]; + + if (folder?.parent && typeof folder?.parent !== 'number' && folder?.parent?.parent) { + data.push([]); + } + + if (folder?.parent && typeof folder.parent !== 'number') { + data.push({ + id: folder.parent.id, + label: folder.parent.name, + href: getFolderURL(pathname, query || {}, { + folder: folder.parent.id?.toString(), + folderPath: folder.parent.path, + }), + }); + } + + if (folder) { + data.push({ + id: folder.id, + label: folder.name, + }); + } + + return data; +}; + +export default getBreadcrumbDataML; diff --git a/packages/core/upload/admin/src/utils/getFileExtension.js b/packages/core/upload/admin/src/utils/getFileExtension.js deleted file mode 100644 index 6c0c2db671..0000000000 --- a/packages/core/upload/admin/src/utils/getFileExtension.js +++ /dev/null @@ -1,3 +0,0 @@ -const getFileExtension = (ext) => (ext && ext[0] === '.' ? ext.substring(1) : ext); - -export default getFileExtension; diff --git a/packages/core/upload/admin/src/utils/getFileExtension.ts b/packages/core/upload/admin/src/utils/getFileExtension.ts new file mode 100644 index 0000000000..86a6db1f14 --- /dev/null +++ b/packages/core/upload/admin/src/utils/getFileExtension.ts @@ -0,0 +1,2 @@ +export const getFileExtension = (ext?: string | null) => + ext && ext[0] === '.' ? ext.substring(1) : ext; diff --git a/packages/core/upload/admin/src/utils/getFolderParents.js b/packages/core/upload/admin/src/utils/getFolderParents.ts similarity index 54% rename from packages/core/upload/admin/src/utils/getFolderParents.js rename to packages/core/upload/admin/src/utils/getFolderParents.ts index c4acf5d937..ddafc4e045 100644 --- a/packages/core/upload/admin/src/utils/getFolderParents.js +++ b/packages/core/upload/admin/src/utils/getFolderParents.ts @@ -1,6 +1,13 @@ import flattenTree from '../components/SelectTree/utils/flattenTree'; -const getFolderParents = (folders, currentFolderId) => { +import type { FolderNode } from '../../../shared/contracts/folders'; + +interface FolderStructureValue extends Omit { + value: number | null; + children?: FolderStructureValue[]; +} + +export const getFolderParents = (folders: FolderStructureValue[], currentFolderId: number) => { const parents = []; const flatFolders = flattenTree(folders); const currentFolder = flatFolders.find((folder) => folder.value === currentFolderId); @@ -14,11 +21,9 @@ const getFolderParents = (folders, currentFolderId) => { while (parent !== undefined) { // eslint-disable-next-line no-loop-func let parentToStore = flatFolders.find(({ value }) => value === parent); - parents.push({ id: parentToStore.value, label: parentToStore.label }); - parent = parentToStore.parent; + parents.push({ id: parentToStore?.value, label: parentToStore?.label }); + parent = parentToStore?.parent; } return parents.reverse(); }; - -export default getFolderParents; diff --git a/packages/core/upload/admin/src/utils/getFolderURL.js b/packages/core/upload/admin/src/utils/getFolderURL.ts similarity index 56% rename from packages/core/upload/admin/src/utils/getFolderURL.js rename to packages/core/upload/admin/src/utils/getFolderURL.ts index ebaff04259..cf1ef8d61f 100644 --- a/packages/core/upload/admin/src/utils/getFolderURL.js +++ b/packages/core/upload/admin/src/utils/getFolderURL.ts @@ -1,16 +1,12 @@ -/** - * @param {string} pathname - * @param {object} currentQuery - * @param {string} query._q Search value of the query - * @param {object} newQuery - * @param {string} newQuery.folder - * @param {string} newQuery.folderPath - * @returns {string} - */ +import type { Query } from '../../../shared/contracts/files'; import { stringify } from 'qs'; -const getFolderURL = (pathname, currentQuery, { folder, folderPath } = {}) => { +export const getFolderURL = ( + pathname: string, + currentQuery: Query, + { folder, folderPath }: { folder?: string; folderPath?: string } = {} +) => { const { _q, ...queryParamsWithoutQ } = currentQuery; const queryParamsString = stringify( { @@ -25,5 +21,3 @@ const getFolderURL = (pathname, currentQuery, { folder, folderPath } = {}) => { // we remove it here to allow navigating in a folder and see the result of this navigation return `${pathname}${queryParamsString ? `?${queryParamsString}` : ''}`; }; - -export default getFolderURL; diff --git a/packages/core/upload/admin/src/utils/getTrad.js b/packages/core/upload/admin/src/utils/getTrad.js deleted file mode 100644 index d0a071b26a..0000000000 --- a/packages/core/upload/admin/src/utils/getTrad.js +++ /dev/null @@ -1,5 +0,0 @@ -import pluginId from '../pluginId'; - -const getTrad = (id) => `${pluginId}.${id}`; - -export default getTrad; diff --git a/packages/core/upload/admin/src/utils/getTrad.ts b/packages/core/upload/admin/src/utils/getTrad.ts new file mode 100644 index 0000000000..85232e1223 --- /dev/null +++ b/packages/core/upload/admin/src/utils/getTrad.ts @@ -0,0 +1,3 @@ +import pluginId from '../pluginId'; + +export const getTrad = (id: string) => `${pluginId}.${id}`; diff --git a/packages/core/upload/admin/src/utils/index.js b/packages/core/upload/admin/src/utils/index.js deleted file mode 100644 index 129ce683fd..0000000000 --- a/packages/core/upload/admin/src/utils/index.js +++ /dev/null @@ -1,16 +0,0 @@ -export { appendSearchParamsToUrl } from './appendSearchParamsToUrl'; -export { default as containsAssetFilter } from './containsAssetFilter'; -export { default as createAssetUrl } from './createAssetUrl'; -export { default as findRecursiveFolderByValue } from './findRecursiveFolderByValue'; -export { default as findRecursiveFolderMetadatas } from './findRecursiveFolderMetadatas'; -export { default as formatBytes } from './formatBytes'; -export * from './formatDuration'; -export { default as getBreadcrumbDataCM } from './getBreadcrumbDataCM'; -export { default as getBreadcrumbDataML } from './getBreadcrumbDataML'; -export { default as getFolderParents } from './getFolderParents'; -export { default as getFolderURL } from './getFolderURL'; -export { default as getTrad } from './getTrad'; -export { default as toSingularTypes } from './toSingularTypes'; -export { default as getFileExtension } from './getFileExtension'; -export { default as prefixFileUrlWithBackendUrl } from './prefixFileUrlWithBackendUrl'; -export { default as getAPIInnerErrors } from './getAPIInnerErrors'; diff --git a/packages/core/upload/admin/src/utils/index.ts b/packages/core/upload/admin/src/utils/index.ts new file mode 100644 index 0000000000..78fbdf05cb --- /dev/null +++ b/packages/core/upload/admin/src/utils/index.ts @@ -0,0 +1,25 @@ +export * from './appendSearchParamsToUrl'; +export * from './containsAssetFilter'; +export * from './createAssetUrl'; +export * from './displayedFilters'; +export * from './downloadFile'; +export * from './findRecursiveFolderByValue'; +export * from './formatBytes'; +export * from './formatDuration'; +export * from './getAllowedFiles'; +export * from './getAPIInnerErrors'; +export * from './getBreadcrumbDataCM'; +export * from './getBreadcrumbDataML'; +export * from './getFileExtension'; +export * from './getFolderParents'; +export * from './getFolderURL'; +export * from './getTrad'; +export * from './moveElement'; +export * from './normalizeAPIError'; +export * from './prefixFileUrlWithBackendUrl'; +export * from './prefixPluginTranslations'; +export * from './rawFileToAsset'; +export * from './toSingularTypes'; +export * from './typeFromMime'; +export * from './urlsToAssets'; +export * from './urlYupSchema'; diff --git a/packages/core/upload/admin/src/utils/moveElement.js b/packages/core/upload/admin/src/utils/moveElement.ts similarity index 59% rename from packages/core/upload/admin/src/utils/moveElement.js rename to packages/core/upload/admin/src/utils/moveElement.ts index 4c6c2a2c27..0ecb89d0fa 100644 --- a/packages/core/upload/admin/src/utils/moveElement.js +++ b/packages/core/upload/admin/src/utils/moveElement.ts @@ -1,4 +1,4 @@ -const move = (array, oldIndex, newIndex) => { +const move = (array: number[], oldIndex: number, newIndex: number) => { if (newIndex >= array.length) { newIndex = array.length - 1; } @@ -7,7 +7,7 @@ const move = (array, oldIndex, newIndex) => { return array; }; -export const moveElement = (array, index, offset) => { +export const moveElement = (array: number[], index: number, offset: number) => { const newIndex = index + offset; return move(array, index, newIndex); diff --git a/packages/core/upload/admin/src/utils/normalizeAPIError.js b/packages/core/upload/admin/src/utils/normalizeAPIError.js deleted file mode 100644 index 7551561f3e..0000000000 --- a/packages/core/upload/admin/src/utils/normalizeAPIError.js +++ /dev/null @@ -1,53 +0,0 @@ -function getPrefixedId(message, callback) { - const prefixedMessage = `apiError.${message}`; - - // if a prefix function has been passed in it is used to - // prefix the id, e.g. to allow an error message to be - // set only for a localization namespace - if (typeof callback === 'function') { - return callback(prefixedMessage); - } - - return prefixedMessage; -} - -function normalizeError(error, { name, intlMessagePrefixCallback }) { - const { message } = error; - - const normalizedError = { - id: getPrefixedId(message, intlMessagePrefixCallback), - defaultMessage: message, - name: error.name ?? name, - values: {}, - }; - - if ('path' in error) { - normalizedError.values = { path: error.path.join('.') }; - } - - return normalizedError; -} - -const validateErrorIsYupValidationError = (err) => - typeof err.details === 'object' && err.details !== null && 'errors' in err.details; - -export function normalizeAPIError(apiError, intlMessagePrefixCallback) { - const error = apiError.response?.data.error; - - if (error) { - // some errors carry multiple errors (such as ValidationError) - if (validateErrorIsYupValidationError(error)) { - return { - name: error.name, - message: error?.message || null, - errors: error.details.errors.map((err) => - normalizeError(err, { name: error.name, intlMessagePrefixCallback }) - ), - }; - } - - return normalizeError(error, { intlMessagePrefixCallback }); - } - - return null; -} diff --git a/packages/core/upload/admin/src/utils/normalizeAPIError.ts b/packages/core/upload/admin/src/utils/normalizeAPIError.ts new file mode 100644 index 0000000000..5d590d6784 --- /dev/null +++ b/packages/core/upload/admin/src/utils/normalizeAPIError.ts @@ -0,0 +1,92 @@ +import type { errors } from '@strapi/utils'; +import type { FetchError } from '@strapi/admin/strapi-admin'; + +type ApiError = InstanceType<(typeof errors)[keyof typeof errors]>; + +interface NormalizeErrorOptions { + name?: string; + intlMessagePrefixCallback?: (id: string) => string; +} + +interface NormalizeErrorReturn { + id: string; + defaultMessage: string; + name?: string; + values: Record<'path', string> | Record; +} + +interface YupFormattedError { + path: string[]; + message: string; + name: string; +} + +function getPrefixedId(message: string, callback?: (prefixedMessage: string) => string) { + const prefixedMessage = `apiError.${message}`; + + // if a prefix function has been passed in it is used to + // prefix the id, e.g. to allow an error message to be + // set only for a localization namespace + if (typeof callback === 'function') { + return callback(prefixedMessage); + } + + return prefixedMessage; +} + +function normalizeError( + error: ApiError | YupFormattedError, + { name, intlMessagePrefixCallback }: NormalizeErrorOptions +): NormalizeErrorReturn { + const { message } = error; + + const normalizedError = { + id: getPrefixedId(message, intlMessagePrefixCallback), + defaultMessage: message, + name: error.name ?? name, + values: {}, + }; + + if ('path' in error) { + normalizedError.values = { path: error.path.join('.') }; + } + + return normalizedError; +} + +const validateErrorIsYupValidationError = ( + err: ApiError +): err is errors.YupValidationError & { details: { errors: YupFormattedError[] } } => + typeof err.details === 'object' && err.details !== null && 'errors' in err.details; + +/** + * Normalize the format of `ResponseError` + * in places where the hook `useAPIErrorHandler` can not called + * (e.g. outside of a React component). + */ +export function normalizeAPIError( + apiError: FetchError, + intlMessagePrefixCallback?: NormalizeErrorOptions['intlMessagePrefixCallback'] +): + | NormalizeErrorReturn + | { name: string; message: string | null; errors: NormalizeErrorReturn[] } + | null { + const error = apiError.response?.data.error; + + if (error) { + // some errors carry multiple errors (such as ValidationError) + if (validateErrorIsYupValidationError(error)) { + return { + name: error.name, + message: error?.message || null, + errors: error.details.errors.map((err) => + normalizeError(err, { name: error.name, intlMessagePrefixCallback }) + ), + }; + } + + return normalizeError(error, { intlMessagePrefixCallback }); + } + + return null; +} diff --git a/packages/core/upload/admin/src/utils/prefixFileUrlWithBackendUrl.js b/packages/core/upload/admin/src/utils/prefixFileUrlWithBackendUrl.js deleted file mode 100644 index 9a1c48f941..0000000000 --- a/packages/core/upload/admin/src/utils/prefixFileUrlWithBackendUrl.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Create the file URL with the backend URL - * @param {Object} asset - * @param {String} fileURL - if true, return the file URL with the Backend url - * if there's no file url or it doesn't start with a slash return the original file url. - * @return {String} file Url - */ -const prefixFileUrlWithBackendUrl = (fileURL) => { - return !!fileURL && fileURL.startsWith('/') ? `${window.strapi.backendURL}${fileURL}` : fileURL; -}; - -export default prefixFileUrlWithBackendUrl; diff --git a/packages/core/upload/admin/src/utils/prefixFileUrlWithBackendUrl.ts b/packages/core/upload/admin/src/utils/prefixFileUrlWithBackendUrl.ts new file mode 100644 index 0000000000..cfcf1b47bc --- /dev/null +++ b/packages/core/upload/admin/src/utils/prefixFileUrlWithBackendUrl.ts @@ -0,0 +1,3 @@ +export const prefixFileUrlWithBackendUrl = (fileURL?: string) => { + return !!fileURL && fileURL.startsWith('/') ? `${window.strapi.backendURL}${fileURL}` : fileURL; +}; diff --git a/packages/core/upload/admin/src/utils/prefixPluginTranslations.js b/packages/core/upload/admin/src/utils/prefixPluginTranslations.js deleted file mode 100644 index 701d6c955b..0000000000 --- a/packages/core/upload/admin/src/utils/prefixPluginTranslations.js +++ /dev/null @@ -1,13 +0,0 @@ -const prefixPluginTranslations = (trad, pluginId) => { - if (!pluginId) { - throw new TypeError("pluginId can't be empty"); - } - - return Object.keys(trad).reduce((acc, current) => { - acc[`${pluginId}.${current}`] = trad[current]; - - return acc; - }, {}); -}; - -export { prefixPluginTranslations }; diff --git a/packages/core/upload/admin/src/utils/prefixPluginTranslations.ts b/packages/core/upload/admin/src/utils/prefixPluginTranslations.ts new file mode 100644 index 0000000000..4626ec3b11 --- /dev/null +++ b/packages/core/upload/admin/src/utils/prefixPluginTranslations.ts @@ -0,0 +1,15 @@ +type Translations = { + [key: string]: string; +}; + +export const prefixPluginTranslations = (trad: Translations, pluginId?: string) => { + if (!pluginId) { + throw new TypeError("pluginId can't be empty"); + } + + return Object.keys(trad).reduce((acc: Translations, current: string) => { + acc[`${pluginId}.${current}`] = trad[current]; + + return acc; + }, {}); +}; diff --git a/packages/core/upload/admin/src/utils/rawFileToAsset.js b/packages/core/upload/admin/src/utils/rawFileToAsset.ts similarity index 57% rename from packages/core/upload/admin/src/utils/rawFileToAsset.js rename to packages/core/upload/admin/src/utils/rawFileToAsset.ts index d585c1828b..8c76c06b85 100644 --- a/packages/core/upload/admin/src/utils/rawFileToAsset.js +++ b/packages/core/upload/admin/src/utils/rawFileToAsset.ts @@ -1,6 +1,9 @@ +// TODO: replace this import with the import from constants file when it will be migrated to TS +import { AssetSource } from '../newConstants'; import { typeFromMime } from './typeFromMime'; +import type { RawFile } from '../../../shared/contracts/files'; -export const rawFileToAsset = (rawFile, assetSource) => { +export const rawFileToAsset = (rawFile: RawFile, assetSource: AssetSource) => { return { size: rawFile.size / 1000, createdAt: new Date(rawFile.lastModified).toISOString(), diff --git a/packages/core/upload/admin/src/utils/tests/appendSearchParamsToUrl.test.js b/packages/core/upload/admin/src/utils/tests/appendSearchParamsToUrl.test.ts similarity index 97% rename from packages/core/upload/admin/src/utils/tests/appendSearchParamsToUrl.test.js rename to packages/core/upload/admin/src/utils/tests/appendSearchParamsToUrl.test.ts index 0f63f0d1ef..92fe227e71 100644 --- a/packages/core/upload/admin/src/utils/tests/appendSearchParamsToUrl.test.js +++ b/packages/core/upload/admin/src/utils/tests/appendSearchParamsToUrl.test.ts @@ -1,4 +1,4 @@ -import { appendSearchParamsToUrl } from '..'; +import { appendSearchParamsToUrl } from '../appendSearchParamsToUrl'; describe('appendSearchParamsToUrl', () => { const updateTime = '2023-07-19T03:00:00.000Z'; @@ -47,7 +47,7 @@ describe('appendSearchParamsToUrl', () => { }); describe('relativeURL', () => { - let originalBackendURL; + let originalBackendURL: string; beforeAll(() => { /** diff --git a/packages/core/upload/admin/src/utils/tests/containsAssetOnlyFilter.test.js b/packages/core/upload/admin/src/utils/tests/containsAssetFilter.test.ts similarity index 93% rename from packages/core/upload/admin/src/utils/tests/containsAssetOnlyFilter.test.js rename to packages/core/upload/admin/src/utils/tests/containsAssetFilter.test.ts index 3e1e2e5899..2de4911766 100644 --- a/packages/core/upload/admin/src/utils/tests/containsAssetOnlyFilter.test.js +++ b/packages/core/upload/admin/src/utils/tests/containsAssetFilter.test.ts @@ -1,4 +1,4 @@ -import { containsAssetFilter } from '..'; +import { containsAssetFilter } from '../containsAssetFilter'; describe('containsAssetFilter', () => { test('does not fail on empty query objects', () => { diff --git a/packages/core/upload/admin/src/utils/tests/downloadFile.test.js b/packages/core/upload/admin/src/utils/tests/downloadFile.test.ts similarity index 91% rename from packages/core/upload/admin/src/utils/tests/downloadFile.test.js rename to packages/core/upload/admin/src/utils/tests/downloadFile.test.ts index fa7ca36cc8..c61437f0e9 100644 --- a/packages/core/upload/admin/src/utils/tests/downloadFile.test.js +++ b/packages/core/upload/admin/src/utils/tests/downloadFile.test.ts @@ -10,11 +10,11 @@ describe('downloadFile', () => { documentSpy.mockReturnValue({ click: clickSpy, - set href(val) { + set href(val: string) { hrefSpy(val); }, setAttribute: setAttributeSpy, - }); + } as unknown as HTMLAnchorElement); await downloadFile('/some/file', 'my-filename'); diff --git a/packages/core/upload/admin/src/utils/tests/findRecursiveFolderByValue.test.js b/packages/core/upload/admin/src/utils/tests/findRecursiveFolderByValue.test.ts similarity index 91% rename from packages/core/upload/admin/src/utils/tests/findRecursiveFolderByValue.test.js rename to packages/core/upload/admin/src/utils/tests/findRecursiveFolderByValue.test.ts index 228fba0850..ccb676b6ba 100644 --- a/packages/core/upload/admin/src/utils/tests/findRecursiveFolderByValue.test.js +++ b/packages/core/upload/admin/src/utils/tests/findRecursiveFolderByValue.test.ts @@ -1,4 +1,4 @@ -import findRecursiveFolderByValue from '../findRecursiveFolderByValue'; +import { findRecursiveFolderByValue } from '../findRecursiveFolderByValue'; const FIXTURE = [ { diff --git a/packages/core/upload/admin/src/utils/tests/findRecursiveFolderMetadatas.test.js b/packages/core/upload/admin/src/utils/tests/findRecursiveFolderMetadatas.test.js deleted file mode 100644 index 5fb91fc69f..0000000000 --- a/packages/core/upload/admin/src/utils/tests/findRecursiveFolderMetadatas.test.js +++ /dev/null @@ -1,51 +0,0 @@ -import findRecursiveFolderMetadatas from '../findRecursiveFolderMetadatas'; - -const FIXTURE_STRUCTURE = { - value: null, - label: 'Media Library', - children: [ - { - value: 1, - label: 'Cats', - children: [ - { - value: 2, - label: 'Michka', - children: [], - }, - ], - }, - ], -}; - -describe('ML || utils || findRecursiveFolderMetadatas', () => { - test('should return parent folder id and label', () => { - const result = findRecursiveFolderMetadatas(FIXTURE_STRUCTURE, 2); - - expect(result).toEqual({ - parentId: 1, - currentFolderLabel: 'Michka', - }); - }); - - test('should return parent id null if parent is root ML', () => { - const result = findRecursiveFolderMetadatas(FIXTURE_STRUCTURE, 1); - - expect(result).toEqual({ - currentFolderLabel: 'Cats', - parentId: null, - }); - }); - - test('should return null if searched id does not exist', () => { - const result = findRecursiveFolderMetadatas(FIXTURE_STRUCTURE, 10); - - expect(result).toEqual(null); - }); - - test('should return null if searched id does not exist (nullish)', () => { - const result = findRecursiveFolderMetadatas(FIXTURE_STRUCTURE, null); - - expect(result).toEqual(null); - }); -}); diff --git a/packages/core/upload/admin/src/utils/tests/formatBytes.test.js b/packages/core/upload/admin/src/utils/tests/formatBytes.test.ts similarity index 95% rename from packages/core/upload/admin/src/utils/tests/formatBytes.test.js rename to packages/core/upload/admin/src/utils/tests/formatBytes.test.ts index bc0e789971..e2b46e5a76 100644 --- a/packages/core/upload/admin/src/utils/tests/formatBytes.test.js +++ b/packages/core/upload/admin/src/utils/tests/formatBytes.test.ts @@ -1,4 +1,4 @@ -import formatBytes from '../formatBytes'; +import { formatBytes } from '../formatBytes'; describe('UPLOAD | components | EditForm | utils', () => { describe('formatBytes', () => { diff --git a/packages/core/upload/admin/src/utils/tests/formatDuration.test.js b/packages/core/upload/admin/src/utils/tests/formatDuration.test.ts similarity index 100% rename from packages/core/upload/admin/src/utils/tests/formatDuration.test.js rename to packages/core/upload/admin/src/utils/tests/formatDuration.test.ts diff --git a/packages/core/upload/admin/src/utils/tests/getAPIInnerErrors.test.js b/packages/core/upload/admin/src/utils/tests/getAPIInnerErrors.test.ts similarity index 94% rename from packages/core/upload/admin/src/utils/tests/getAPIInnerErrors.test.js rename to packages/core/upload/admin/src/utils/tests/getAPIInnerErrors.test.ts index dbcb4de4cd..7d8e2d3c54 100644 --- a/packages/core/upload/admin/src/utils/tests/getAPIInnerErrors.test.js +++ b/packages/core/upload/admin/src/utils/tests/getAPIInnerErrors.test.ts @@ -1,6 +1,6 @@ import { FetchError } from '@strapi/admin/strapi-admin'; -import getAPIInnerErrors from '../getAPIInnerErrors'; +import { getAPIInnerErrors } from '../getAPIInnerErrors'; const API_VALIDATION_ERROR_FIXTURE = new FetchError('ValidationError', { data: { @@ -24,7 +24,6 @@ const API_VALIDATION_ERROR_FIXTURE = new FetchError('ValidationError', { }, }, }, - status: 422, }); const API_APPLICATION_ERROR_FIXTURE = new FetchError('ApplicationError', { @@ -35,7 +34,6 @@ const API_APPLICATION_ERROR_FIXTURE = new FetchError('ApplicationError', { details: {}, }, }, - status: 400, }); describe('getAPIInnerError', () => { diff --git a/packages/core/upload/admin/src/utils/tests/getAllowedFiles.test.js b/packages/core/upload/admin/src/utils/tests/getAllowedFiles.test.js deleted file mode 100644 index 59101733f9..0000000000 --- a/packages/core/upload/admin/src/utils/tests/getAllowedFiles.test.js +++ /dev/null @@ -1,153 +0,0 @@ -import getAllowedFiles from '../getAllowedFiles'; - -const files = [ - { - id: 1, - mime: 'application', - }, - { - id: 2, - mime: 'application', - }, - { - id: 3, - mime: 'image/png', - }, - { - id: 4, - mime: 'video/mov', - }, - { - id: 5, - mime: 'image/jpg', - }, - { - id: 6, - mime: 'image/test', - }, - { - id: 7, - mime: 'audio/mpeg', - }, - { - id: 8, - mime: 'audio/x-wav', - }, - { - id: 9, - mime: 'audio/ogg', - }, -]; - -describe('UPLOAD | components | MediaLibraryInput | utils | getAllowedFiles', () => { - it('returns an empty array of when the allowed files is empty', () => { - const results = getAllowedFiles([], files); - - expect(results).toEqual([]); - }); - - it('returns an array with elements that are not video or image when the allowedTypes is files', () => { - const results = getAllowedFiles(['files'], files); - - expect(results).toEqual([ - { - id: 1, - mime: 'application', - }, - { - id: 2, - mime: 'application', - }, - ]); - }); - - it('returns an array with elements that are only video when the allowedTypes is videos', () => { - const results = getAllowedFiles(['videos'], files); - - expect(results).toEqual([ - { - id: 4, - mime: 'video/mov', - }, - ]); - }); - - it('returns an array with elements that are only video when the allowedTypes is videos', () => { - const results = getAllowedFiles(['audios'], files); - - expect(results).toEqual([ - { - id: 7, - mime: 'audio/mpeg', - }, - { - id: 8, - mime: 'audio/x-wav', - }, - { - id: 9, - mime: 'audio/ogg', - }, - ]); - }); - - it('returns an array with elements that are only image when the allowedTypes is images', () => { - const results = getAllowedFiles(['images'], files); - - expect(results).toEqual([ - { - id: 3, - mime: 'image/png', - }, - { - id: 5, - mime: 'image/jpg', - }, - { - id: 6, - mime: 'image/test', - }, - ]); - }); - - it('returns an array with elements that are image and video when the allowedTypes are videos and images', () => { - const results = getAllowedFiles(['videos', 'images', 'audios'], files); - - expect(results).toEqual([ - { - id: 3, - mime: 'image/png', - }, - { - id: 4, - mime: 'video/mov', - }, - { - id: 5, - mime: 'image/jpg', - }, - { - id: 6, - mime: 'image/test', - }, - { - id: 7, - mime: 'audio/mpeg', - }, - { - id: 8, - mime: 'audio/x-wav', - }, - { - id: 9, - mime: 'audio/ogg', - }, - ]); - }); - - it('returns an array with all the elements', () => { - const results = getAllowedFiles(['videos', 'images', 'files', 'audios'], files); - - expect(results).toEqual(files); - }); -}); diff --git a/packages/core/upload/admin/src/utils/tests/getAllowedFiles.test.ts b/packages/core/upload/admin/src/utils/tests/getAllowedFiles.test.ts new file mode 100644 index 0000000000..07e184da08 --- /dev/null +++ b/packages/core/upload/admin/src/utils/tests/getAllowedFiles.test.ts @@ -0,0 +1,133 @@ +import { getAllowedFiles } from '../getAllowedFiles'; + +const COMMON_PROPERTIES = { + size: 100, + createdAt: '2021-09-01T00:00:00.000Z', + updatedAt: '2021-09-01T00:00:00.000Z', + folder: null, + folderPath: '/', + documentId: 'documentId', + hash: 'hash', + locale: null, + provider: 'local', + isSelectable: true, + type: 'asset', +}; + +const FILE_1 = { + id: 1, + mime: 'application', + name: 'file.application', + url: '/uploads/file.application', + ...COMMON_PROPERTIES, +}; + +const FILE_2 = { + id: 2, + mime: 'application', + name: 'file2.application', + url: '/uploads/file2.application', + ...COMMON_PROPERTIES, +}; + +const FILE_3 = { + id: 3, + mime: 'image/png', + name: 'image.png', + url: '/uploads/image.png', + ...COMMON_PROPERTIES, +}; + +const FILE_4 = { + id: 4, + mime: 'video/mov', + name: 'video.mov', + url: '/uploads/video.mov', + ...COMMON_PROPERTIES, +}; + +const FILE_5 = { + id: 5, + mime: 'image/jpg', + name: 'image2.jpg', + url: '/uploads/image2.jpg', + ...COMMON_PROPERTIES, +}; + +const FILE_6 = { + id: 6, + mime: 'image/test', + name: 'image.test', + url: '/uploads/image.test', + ...COMMON_PROPERTIES, +}; + +const FILE_7 = { + id: 7, + mime: 'audio/mpeg', + name: 'audio.mpeg', + url: '/uploads/audio.mpeg', + ...COMMON_PROPERTIES, +}; + +const FILE_8 = { + id: 8, + mime: 'audio/x-wav', + name: 'audio.x-wav', + url: '/uploads/audio.x-wav', + ...COMMON_PROPERTIES, +}; + +const FILE_9 = { + id: 9, + mime: 'audio/ogg', + name: 'audio.ogg', + url: '/uploads/audio.ogg', + ...COMMON_PROPERTIES, +}; + +const files = [FILE_1, FILE_2, FILE_3, FILE_4, FILE_5, FILE_6, FILE_7, FILE_8, FILE_9]; + +describe('UPLOAD | components | MediaLibraryInput | utils | getAllowedFiles', () => { + it('returns an empty array of when the allowed files is empty', () => { + const results = getAllowedFiles([], files); + + expect(results).toEqual([]); + }); + + it('returns an array with elements that are not video or image when the allowedTypes is files', () => { + const results = getAllowedFiles(['files'], files); + + expect(results).toEqual([FILE_1, FILE_2]); + }); + + it('returns an array with elements that are only video when the allowedTypes is videos', () => { + const results = getAllowedFiles(['videos'], files); + + expect(results).toEqual([FILE_4]); + }); + + it('returns an array with elements that are only video when the allowedTypes is videos', () => { + const results = getAllowedFiles(['audios'], files); + + expect(results).toEqual([FILE_7, FILE_8, FILE_9]); + }); + + it('returns an array with elements that are only image when the allowedTypes is images', () => { + const results = getAllowedFiles(['images'], files); + + expect(results).toEqual([FILE_3, FILE_5, FILE_6]); + }); + + it('returns an array with elements that are image and video when the allowedTypes are videos and images', () => { + const results = getAllowedFiles(['videos', 'images', 'audios'], files); + + expect(results).toEqual([FILE_3, FILE_4, FILE_5, FILE_6, FILE_7, FILE_8, FILE_9]); + }); + + it('returns an array with all the elements', () => { + const results = getAllowedFiles(['videos', 'images', 'files', 'audios'], files); + + expect(results).toEqual(files); + }); +}); diff --git a/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataCM.test.js b/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataCM.test.ts similarity index 96% rename from packages/core/upload/admin/src/utils/tests/getBreadcrumbDataCM.test.js rename to packages/core/upload/admin/src/utils/tests/getBreadcrumbDataCM.test.ts index 92d9611a4d..2d51acf6c8 100644 --- a/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataCM.test.js +++ b/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataCM.test.ts @@ -1,9 +1,10 @@ -import { getBreadcrumbDataCM } from '..'; +import { getBreadcrumbDataCM } from '../getBreadcrumbDataCM'; const FIXTURE_FOLDER = { id: 1, name: 'first-level', path: '/1 ', + pathId: 1, }; describe('getBreadcrumbDataCM', () => { diff --git a/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataML.test.js b/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataML.test.ts similarity index 97% rename from packages/core/upload/admin/src/utils/tests/getBreadcrumbDataML.test.js rename to packages/core/upload/admin/src/utils/tests/getBreadcrumbDataML.test.ts index 20b6ee534e..ce3744f9c4 100644 --- a/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataML.test.js +++ b/packages/core/upload/admin/src/utils/tests/getBreadcrumbDataML.test.ts @@ -1,4 +1,4 @@ -import { getBreadcrumbDataML } from '..'; +import { getBreadcrumbDataML } from '../getBreadcrumbDataML'; const FIXTURE_PATHNAME = '/media-library'; const FIXTURE_QUERY = { diff --git a/packages/core/upload/admin/src/utils/tests/getFileExtension.test.js b/packages/core/upload/admin/src/utils/tests/getFileExtension.test.ts similarity index 86% rename from packages/core/upload/admin/src/utils/tests/getFileExtension.test.js rename to packages/core/upload/admin/src/utils/tests/getFileExtension.test.ts index e770afdc92..c29c2a33a9 100644 --- a/packages/core/upload/admin/src/utils/tests/getFileExtension.test.js +++ b/packages/core/upload/admin/src/utils/tests/getFileExtension.test.ts @@ -1,11 +1,10 @@ -import getFileExtension from '../getFileExtension'; +import { getFileExtension } from '../getFileExtension'; describe('getFileExtension', () => { it('should return undefined if ext does not exits', () => { const ext = null; const expected = null; - // @ts-expect-error ext should be a string so will throw error that ext is null. expect(getFileExtension(ext)).toEqual(expected); }); diff --git a/packages/core/upload/admin/src/utils/tests/getFolderParents.test.js b/packages/core/upload/admin/src/utils/tests/getFolderParents.test.ts similarity index 96% rename from packages/core/upload/admin/src/utils/tests/getFolderParents.test.js rename to packages/core/upload/admin/src/utils/tests/getFolderParents.test.ts index d8f17cc2bf..098b91f28c 100644 --- a/packages/core/upload/admin/src/utils/tests/getFolderParents.test.js +++ b/packages/core/upload/admin/src/utils/tests/getFolderParents.test.ts @@ -1,4 +1,4 @@ -import getFolderParents from '../getFolderParents'; +import { getFolderParents } from '../getFolderParents'; const FIXTURE_FOLDER_STRUCTURE = [ { diff --git a/packages/core/upload/admin/src/utils/tests/getFolderURL.test.js b/packages/core/upload/admin/src/utils/tests/getFolderURL.test.ts similarity index 83% rename from packages/core/upload/admin/src/utils/tests/getFolderURL.test.js rename to packages/core/upload/admin/src/utils/tests/getFolderURL.test.ts index 9fb3c92505..8a2a9e0d59 100644 --- a/packages/core/upload/admin/src/utils/tests/getFolderURL.test.js +++ b/packages/core/upload/admin/src/utils/tests/getFolderURL.test.ts @@ -1,8 +1,8 @@ -import { getFolderURL } from '..'; +import { getFolderURL } from '../getFolderURL'; const FIXTURE_PATHNAME = '/media-library'; const FIXTURE_QUERY = {}; -const FIXTURE_FOLDER = 1; +const FIXTURE_FOLDER = '1'; const FIXTURE_FOLDER_PATH = '/1/2/3'; describe('getFolderURL', () => { @@ -24,12 +24,8 @@ describe('getFolderURL', () => { test('keeps and stringifies query parameter', () => { expect( - getFolderURL( - FIXTURE_PATHNAME, - { ...FIXTURE_QUERY, some: 'thing' }, - { folder: FIXTURE_FOLDER } - ) - ).toMatchInlineSnapshot(`"/media-library?some=thing&folder=1"`); + getFolderURL(FIXTURE_PATHNAME, { ...FIXTURE_QUERY }, { folder: FIXTURE_FOLDER }) + ).toMatchInlineSnapshot(`"/media-library?folder=1"`); }); test('includes folderPath if provided', () => { diff --git a/packages/core/upload/admin/src/utils/tests/normalizeAPIError.test.js b/packages/core/upload/admin/src/utils/tests/normalizeAPIError.test.ts similarity index 95% rename from packages/core/upload/admin/src/utils/tests/normalizeAPIError.test.js rename to packages/core/upload/admin/src/utils/tests/normalizeAPIError.test.ts index b70e094046..10f15990c7 100644 --- a/packages/core/upload/admin/src/utils/tests/normalizeAPIError.test.js +++ b/packages/core/upload/admin/src/utils/tests/normalizeAPIError.test.ts @@ -24,7 +24,6 @@ const API_VALIDATION_ERROR_FIXTURE = new FetchError('ValidationError', { }, }, }, - status: 422, }); const API_APPLICATION_ERROR_FIXTURE = new FetchError('ApplicationError', { @@ -35,7 +34,6 @@ const API_APPLICATION_ERROR_FIXTURE = new FetchError('ApplicationError', { details: {}, }, }, - status: 400, }); describe('normalizeAPIError', () => { @@ -66,7 +64,7 @@ describe('normalizeAPIError', () => { }); test('Handle ValidationError with custom prefix function', () => { - const prefixFunction = (id) => `custom.${id}`; + const prefixFunction = (id: string) => `custom.${id}`; expect(normalizeAPIError(API_VALIDATION_ERROR_FIXTURE, prefixFunction)).toStrictEqual({ name: 'ValidationError', @@ -103,7 +101,7 @@ describe('normalizeAPIError', () => { }); test('Handle ApplicationError with custom prefix function', () => { - const prefixFunction = (id) => `custom.${id}`; + const prefixFunction = (id: string) => `custom.${id}`; expect(normalizeAPIError(API_APPLICATION_ERROR_FIXTURE, prefixFunction)).toStrictEqual({ name: 'ApplicationError', diff --git a/packages/core/upload/admin/src/utils/tests/prefixFileUrlWithBackendUrl.test.js b/packages/core/upload/admin/src/utils/tests/prefixFileUrlWithBackendUrl.test.ts similarity index 90% rename from packages/core/upload/admin/src/utils/tests/prefixFileUrlWithBackendUrl.test.js rename to packages/core/upload/admin/src/utils/tests/prefixFileUrlWithBackendUrl.test.ts index 65e688dc46..a8c526b67f 100644 --- a/packages/core/upload/admin/src/utils/tests/prefixFileUrlWithBackendUrl.test.js +++ b/packages/core/upload/admin/src/utils/tests/prefixFileUrlWithBackendUrl.test.ts @@ -1,4 +1,4 @@ -import prefixFileUrlWithBackendUrl from '../prefixFileUrlWithBackendUrl'; +import { prefixFileUrlWithBackendUrl } from '../prefixFileUrlWithBackendUrl'; describe('prefixFileUrlWithBackendUrl', () => { it("should add the strapi back-end url if the file's url startsWith '/'", () => { diff --git a/packages/core/upload/admin/src/utils/tests/prefixPluginTranslations.test.js b/packages/core/upload/admin/src/utils/tests/prefixPluginTranslations.test.ts similarity index 100% rename from packages/core/upload/admin/src/utils/tests/prefixPluginTranslations.test.js rename to packages/core/upload/admin/src/utils/tests/prefixPluginTranslations.test.ts diff --git a/packages/core/upload/admin/src/utils/tests/toSingularTypes.test.js b/packages/core/upload/admin/src/utils/tests/toSingularTypes.test.ts similarity index 87% rename from packages/core/upload/admin/src/utils/tests/toSingularTypes.test.js rename to packages/core/upload/admin/src/utils/tests/toSingularTypes.test.ts index ab4bb43f97..33bfbf5a3d 100644 --- a/packages/core/upload/admin/src/utils/tests/toSingularTypes.test.js +++ b/packages/core/upload/admin/src/utils/tests/toSingularTypes.test.ts @@ -1,4 +1,4 @@ -import toSingularTypes from '../toSingularTypes'; +import { toSingularTypes } from '../toSingularTypes'; describe('UPLOAD | utils | toSingularTypes', () => { it('returns an array', () => { diff --git a/packages/core/upload/admin/src/utils/tests/typeFromMime.test.js b/packages/core/upload/admin/src/utils/tests/typeFromMime.test.ts similarity index 80% rename from packages/core/upload/admin/src/utils/tests/typeFromMime.test.js rename to packages/core/upload/admin/src/utils/tests/typeFromMime.test.ts index 218e5682a3..8fc7f691e0 100644 --- a/packages/core/upload/admin/src/utils/tests/typeFromMime.test.js +++ b/packages/core/upload/admin/src/utils/tests/typeFromMime.test.ts @@ -1,4 +1,5 @@ -import { AssetType } from '../../constants'; +// TODO: replace this import with the import from constants file when it will be migrated to TS +import { AssetType } from '../../newConstants'; import { typeFromMime } from '../typeFromMime'; describe('typeFromMime', () => { diff --git a/packages/core/upload/admin/src/utils/toSingularTypes.js b/packages/core/upload/admin/src/utils/toSingularTypes.js deleted file mode 100644 index 00897bef98..0000000000 --- a/packages/core/upload/admin/src/utils/toSingularTypes.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Transforms an arrays of plural type to singular one - * @param {Object[]} types - * @returns Object[] - */ -const toSingularTypes = (types) => { - if (!types) { - return []; - } - - return types.map((type) => type.substring(0, type.length - 1)); -}; - -export default toSingularTypes; diff --git a/packages/core/upload/admin/src/utils/toSingularTypes.ts b/packages/core/upload/admin/src/utils/toSingularTypes.ts new file mode 100644 index 0000000000..b0a12017eb --- /dev/null +++ b/packages/core/upload/admin/src/utils/toSingularTypes.ts @@ -0,0 +1,9 @@ +export const toSingularTypes = (types?: string[]) => { + if (!types) { + return []; + } + + return types.map((type) => type.substring(0, type.length - 1)); +}; + +export default toSingularTypes; diff --git a/packages/core/upload/admin/src/utils/typeFromMime.js b/packages/core/upload/admin/src/utils/typeFromMime.ts similarity index 56% rename from packages/core/upload/admin/src/utils/typeFromMime.js rename to packages/core/upload/admin/src/utils/typeFromMime.ts index 2e0bc7678d..4a3285e172 100644 --- a/packages/core/upload/admin/src/utils/typeFromMime.js +++ b/packages/core/upload/admin/src/utils/typeFromMime.ts @@ -1,6 +1,7 @@ -import { AssetType } from '../constants'; +// TODO: replace this import with the import from constants file when it will be migrated to TS +import { AssetType } from '../newConstants'; -export const typeFromMime = (mime) => { +export const typeFromMime = (mime: string) => { if (mime.includes(AssetType.Image)) { return AssetType.Image; } diff --git a/packages/core/upload/admin/src/utils/urlYupSchema.js b/packages/core/upload/admin/src/utils/urlYupSchema.ts similarity index 97% rename from packages/core/upload/admin/src/utils/urlYupSchema.js rename to packages/core/upload/admin/src/utils/urlYupSchema.ts index 70330926a0..5829df43ab 100644 --- a/packages/core/upload/admin/src/utils/urlYupSchema.js +++ b/packages/core/upload/admin/src/utils/urlYupSchema.ts @@ -1,7 +1,7 @@ import { translatedErrors as errorsTrads } from '@strapi/admin/strapi-admin'; import * as yup from 'yup'; -import getTrad from './getTrad'; +import { getTrad } from './getTrad'; export const urlSchema = yup.object().shape({ urls: yup.string().test({ diff --git a/packages/core/upload/admin/src/utils/urlsToAssets.js b/packages/core/upload/admin/src/utils/urlsToAssets.ts similarity index 70% rename from packages/core/upload/admin/src/utils/urlsToAssets.js rename to packages/core/upload/admin/src/utils/urlsToAssets.ts index 1316e1ea76..186f22a7f2 100644 --- a/packages/core/upload/admin/src/utils/urlsToAssets.js +++ b/packages/core/upload/admin/src/utils/urlsToAssets.ts @@ -1,18 +1,19 @@ -import { AssetSource } from '../constants'; +// TODO: replace this import with the import from constants file when it will be migrated to TS +import { AssetSource } from '../newConstants'; import { typeFromMime } from './typeFromMime'; -function getFilenameFromURL(url) { +function getFilenameFromURL(url: string) { return new URL(url).pathname.split('/').pop(); } -export const urlsToAssets = async (urls) => { +export const urlsToAssets = async (urls: string[]) => { const assetPromises = urls.map((url) => fetch(url).then(async (res) => { const blob = await res.blob(); - const loadedFile = new File([blob], getFilenameFromURL(res.url), { - type: res.headers.get('content-type'), + const loadedFile = new File([blob], getFilenameFromURL(res.url)!, { + type: res.headers.get('content-type') || undefined, }); return { @@ -29,7 +30,7 @@ export const urlsToAssets = async (urls) => { const assets = assetsResults.map((fullFilledAsset) => ({ source: AssetSource.Url, name: fullFilledAsset.name, - type: typeFromMime(fullFilledAsset.mime), + type: typeFromMime(fullFilledAsset.mime!), url: fullFilledAsset.url, ext: fullFilledAsset.url.split('.').pop(), mime: fullFilledAsset.mime, diff --git a/packages/core/upload/admin/tsconfig.build.json b/packages/core/upload/admin/tsconfig.build.json index 6af52a63b9..22ac65f160 100644 --- a/packages/core/upload/admin/tsconfig.build.json +++ b/packages/core/upload/admin/tsconfig.build.json @@ -5,6 +5,6 @@ "baseUrl": ".", "outDir": "./dist" }, - "include": ["./src", "../shared", "../package.json"], + "include": ["./src", "./custom.d.ts", "../shared", "../package.json"], "exclude": ["**/__mocks__", "./src/**/tests", "**/*.test.*"] } diff --git a/packages/core/upload/admin/tsconfig.json b/packages/core/upload/admin/tsconfig.json index 17d69ac5c6..29a82f3c2c 100644 --- a/packages/core/upload/admin/tsconfig.json +++ b/packages/core/upload/admin/tsconfig.json @@ -7,5 +7,5 @@ "@tests/*": ["./tests/*"] } }, - "include": ["../package.json", "./src", "../shared", "./tests"] + "include": ["../package.json", "./src", "../shared", "./tests", "./custom.d.ts"] } diff --git a/packages/core/upload/package.json b/packages/core/upload/package.json index 44b63ecd01..51651c459e 100644 --- a/packages/core/upload/package.json +++ b/packages/core/upload/package.json @@ -87,6 +87,7 @@ "@testing-library/dom": "10.1.0", "@testing-library/react": "15.0.7", "@testing-library/user-event": "14.5.2", + "@types/byte-size": "8.1.2", "@types/fs-extra": "11.0.4", "@types/koa": "2.13.4", "@types/koa-range": "0.3.5", diff --git a/packages/core/upload/shared/contracts/files.ts b/packages/core/upload/shared/contracts/files.ts index 156a241957..1a6228de5f 100644 --- a/packages/core/upload/shared/contracts/files.ts +++ b/packages/core/upload/shared/contracts/files.ts @@ -4,6 +4,58 @@ type SortOrder = 'ASC' | 'DESC'; type SortKey = 'createdAt' | 'name'; +// Abstract type for comparison operators where the keys are generic strings +type ComparisonOperators = { + [operator: string]: T | T[] | boolean; // Any string can be used as an operator key +}; + +// Abstract type for filter conditions with dynamic field names +export type FilterCondition = { + [field: string]: T | ComparisonOperators | FilterCondition; // Field names are dynamic and values are comparison operators +}; + +// Abstract type for filters where the logical operator (like $and) is a generic string +type Filters = { + [logicOperator: string]: FilterCondition[]; // Logical operator key is a generic string +}; + +export type Query = { + _q?: string; + folderPath?: string; + folder?: + | null + | number + | { + id: number; + }; + page?: + | string + | number + | { + id: string | number; + }; + pageSize?: string | number; + pagination?: { + pageSize: number; + }; + sort?: `${SortKey}:${SortOrder}`; + filters?: Filters; + state?: boolean; +}; + +type FileFormat = { + name: string; + hash: string; + ext: string; + mime: string; + path: null | string; + width: number; + height: number; + size: number; + sizeInBytes: number; + url: string; +}; + export interface File { id: number; name: string; @@ -11,7 +63,7 @@ export interface File { caption?: string | null; width?: number; height?: number; - formats?: Record; + formats?: Record; hash: string; ext?: string; mime?: string; @@ -23,7 +75,7 @@ export interface File { provider?: string; provider_metadata?: Record; isUrlSigned?: boolean; - folder?: number; + folder?: number | null; folderPath?: string; related?: { id: string | number; @@ -33,12 +85,11 @@ export interface File { createdAt?: string; updatedAt?: string; createdBy?: number; + publishedAt?: string; updatedBy?: number; + isLocal?: boolean; } -/** - * GET /upload/files - Get files - */ export interface RawFile extends Blob { size: number; lastModified: number; @@ -53,6 +104,9 @@ export interface Pagination { total: number; } +/** + * GET /upload/files - Get files + */ export declare namespace GetFiles { export interface Request { body: {}; diff --git a/packages/core/upload/shared/contracts/folders.ts b/packages/core/upload/shared/contracts/folders.ts index 5959105eb4..fe2e514f32 100644 --- a/packages/core/upload/shared/contracts/folders.ts +++ b/packages/core/upload/shared/contracts/folders.ts @@ -9,20 +9,20 @@ import type { File } from './files'; export interface Folder { id: number; name: string; - pathId: number; + pathId?: number; /** * parent id */ - parent?: number; + parent?: number | null | Folder; /** * children ids */ children?: number[]; - path: string; + path?: string; files?: File[]; } -type FolderNode = Partial & { +export type FolderNode = Partial> & { children: FolderNode[]; }; diff --git a/yarn.lock b/yarn.lock index 142801e84b..c23e17e447 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8378,6 +8378,7 @@ __metadata: "@testing-library/dom": "npm:10.1.0" "@testing-library/react": "npm:15.0.7" "@testing-library/user-event": "npm:14.5.2" + "@types/byte-size": "npm:8.1.2" "@types/fs-extra": "npm:11.0.4" "@types/koa": "npm:2.13.4" "@types/koa-range": "npm:0.3.5" @@ -8868,6 +8869,13 @@ __metadata: languageName: node linkType: hard +"@types/byte-size@npm:8.1.2": + version: 8.1.2 + resolution: "@types/byte-size@npm:8.1.2" + checksum: 10c0/2a251414d925e02a4b36629c2ea0519efb5006baa82241ae85fdfebb65f43d3679c486e247ff6b82e4fbbac556b9d55adf7c0f254e16d6b1d972ffb96d47b8f2 + languageName: node + linkType: hard + "@types/cacheable-request@npm:^6.0.1": version: 6.0.2 resolution: "@types/cacheable-request@npm:6.0.2"