diff --git a/src/files-and-uploads/FileInfo.jsx b/src/files-and-uploads/FileInfo.jsx index 171958cbba..30f0afe80b 100644 --- a/src/files-and-uploads/FileInfo.jsx +++ b/src/files-and-uploads/FileInfo.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import PropTypes from 'prop-types'; + import { injectIntl, FormattedMessage, @@ -17,37 +18,44 @@ import { CheckboxControl, } from '@edx/paragon'; import { ContentCopy, InfoOutline } from '@edx/paragon/icons'; -import AssetThumbnail from './FileThumbnail'; +import { getFileSizeToClosestByte } from './data/utils'; +import AssetThumbnail from './FileThumbnail'; import messages from './messages'; +import UsageMetricsMessages from './UsageMetricsMessage'; const FileInfo = ({ asset, isOpen, onClose, handleLockedAsset, + usagePathStatus, + error, // injected intl, }) => { - const [lockedState, setLockedState] = useState(asset.locked); + const [lockedState, setLockedState] = useState(asset?.locked); const handleLock = (e) => { const locked = e.target.checked; setLockedState(locked); - handleLockedAsset(asset.id, locked); + handleLockedAsset(asset?.id, locked); }; + const fileSize = getFileSizeToClosestByte(asset?.fileSize); + return (
- {asset.displayName} + {asset?.displayName}
@@ -57,10 +65,10 @@ const FileInfo = ({
@@ -68,7 +76,7 @@ const FileInfo = ({
- {/* {asset.fileSize} */} -
-
+ {fileSize} +
- {asset.portableUrl} + {asset?.portableUrl}
@@ -94,7 +101,7 @@ const FileInfo = ({ src={ContentCopy} iconAs={Icon} alt={messages.copyStudioUrlTitle.defaultMessage} - onClick={() => navigator.clipboard.writeText(asset.portableUrl)} + onClick={() => navigator.clipboard.writeText(asset?.portableUrl)} />
@@ -103,7 +110,7 @@ const FileInfo = ({
- {asset.externalUrl} + {asset?.externalUrl}
@@ -111,11 +118,10 @@ const FileInfo = ({ src={ContentCopy} iconAs={Icon} alt={messages.copyWebUrlTitle.defaultMessage} - onClick={() => navigator.clipboard.writeText(asset.externalUrl)} + onClick={() => navigator.clipboard.writeText(asset?.externalUrl)} />
-
- +
@@ -140,11 +146,11 @@ const FileInfo = ({
+ ); }; - FileInfo.propTypes = { asset: PropTypes.shape({ displayName: PropTypes.string.isRequired, @@ -155,10 +161,14 @@ FileInfo.propTypes = { id: PropTypes.string.isRequired, portableUrl: PropTypes.string.isRequired, dateAdded: PropTypes.string.isRequired, + fileSize: PropTypes.number.isRequired, + usageLocations: PropTypes.arrayOf(PropTypes.string), }).isRequired, onClose: PropTypes.func.isRequired, isOpen: PropTypes.bool.isRequired, handleLockedAsset: PropTypes.func.isRequired, + usagePathStatus: PropTypes.string.isRequired, + error: PropTypes.arrayOf(PropTypes.string).isRequired, // injected intl: intlShape.isRequired, }; diff --git a/src/files-and-uploads/FilesAndUploads.jsx b/src/files-and-uploads/FilesAndUploads.jsx index 4b52c3953e..12a56b8f8b 100644 --- a/src/files-and-uploads/FilesAndUploads.jsx +++ b/src/files-and-uploads/FilesAndUploads.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import _ from 'lodash'; +import isEmpty from 'lodash/isEmpty'; import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; import { DataTable, @@ -22,12 +22,14 @@ import { addAssetFile, deleteAssetFile, fetchAssets, + getUsagePaths, updateAssetLock, updateAssetOrder, } from './data/thunks'; import { sortFiles } from './data/utils'; import messages from './messages'; +import FileInfo from './FileInfo'; import FileInput, { fileInput } from './FileInput'; import FilesAndUploadsProvider from './FilesAndUploadsProvider'; import { @@ -52,6 +54,7 @@ const FilesAndUploads = ({ }; const [currentView, setCurrentView] = useState(defaultVal); const [isDeleteOpen, setDeleteOpen, setDeleteClose] = useToggle(false); + const [isAssetInfoOpen, openAssetInfo, closeAssetinfo] = useToggle(false); const [isAddOpen, setAddOpen, setAddClose] = useToggle(false); const [selectedRows, setSelectedRows] = useState([]); const [isDeleteConfirmationOpen, openDeleteConfirmation, closeDeleteConfirmation] = useToggle(false); @@ -65,9 +68,10 @@ const FilesAndUploads = ({ loadingStatus, addingStatus: addAssetStatus, deletingStatus: deleteAssetStatus, - savingStatus: saveAssetStatus, + updatingStatus: updateAssetStatus, + usageStatus: usagePathStatus, + errors: errorMessages, } = useSelector(state => state.assets); - const errorMessages = useSelector(state => state.assets.errors); const fileInputControl = fileInput({ onAddFile: (file) => dispatch(addAssetFile(courseId, file, totalCount)), setSelectedRows, @@ -118,6 +122,12 @@ const FilesAndUploads = ({ openDeleteConfirmation(); }; + const handleOpenAssetInfo = (original) => { + setSelectedRows([{ original }]); + dispatch(getUsagePaths({ asset: original, courseId, setSelectedRows })); + openAssetInfo(); + }; + const headerActions = ({ selectedFlatRows }) => ( ); } - return (
@@ -171,19 +182,38 @@ const FilesAndUploads = ({ hideHeading={false} isError={addAssetStatus === RequestStatus.FAILED} > - {intl.formatMessage(messages.errorAlertMessage, { message: errorMessages.upload })} +
    + {errorMessages.upload.map(message => ( +
  • + {intl.formatMessage(messages.errorAlertMessage, { message })} +
  • + ))} +
- {intl.formatMessage(messages.errorAlertMessage, { message: errorMessages.delete })} +
    + {errorMessages.delete.map(message => ( +
  • + {intl.formatMessage(messages.errorAlertMessage, { message })} +
  • + ))} +
- {intl.formatMessage(messages.errorAlertMessage, { message: errorMessages.lock })} +
    + {errorMessages.lock.map(message => ( +
  • + {intl.formatMessage(messages.errorAlertMessage, { message })} +
  • + ))} +
+
@@ -236,28 +266,12 @@ const FilesAndUploads = ({ }, ], }, - { - Header: 'Locked', - accessor: 'locked', - // Filter: CheckboxFilter, - // filter: 'exactText', - // filterChoices: [ - // { - // name: 'Locked', - // value: true, - // }, - // { - // name: 'Unlocked', - // value: false, - // }, - // ], - }, ]} itemCount={totalCount} pageCount={Math.ceil(totalCount / 50)} data={assets} > - {_.isEmpty(assets) && loadingStatus !== RequestStatus.IN_PROGRESS ? ( + {isEmpty(assets) && loadingStatus !== RequestStatus.IN_PROGRESS ? ( - + {!isEmpty(selectedRows) && ( + + )} { expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible(); const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); expect(assetMenuButton).toBeVisible(); + axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`).reply(201, { usageLocations: ['subsection - unit / block'] }); await waitFor(() => { fireEvent.click(within(assetMenuButton).getByLabelText('asset-menu-toggle')); fireEvent.click(screen.getByText('Info')); + executeThunk(getUsagePaths({ + courseId, + asset: { id: 'mOckID1', displayName: 'mOckID1' }, + setSelectedRows: jest.fn(), + }), store.dispatch); expect(screen.getAllByLabelText('mOckID1')[0]).toBeVisible(); }); + const { usageStatus } = store.getState().assets; + expect(usageStatus).toEqual(RequestStatus.SUCCESSFUL); + expect(screen.getByText('subsection - unit / block')).toBeVisible(); }); it('should open asset info and handle lock checkbox', async () => { renderComponent(); @@ -254,9 +264,15 @@ describe('FilesAndUploads', () => { const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID1'); expect(assetMenuButton).toBeVisible(); axiosMock.onPut(`${getAssetsUrl(courseId)}mOckID1`).reply(201, { locked: false }); + axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID1/usage`).reply(201, { usageLocations: [] }); await waitFor(() => { fireEvent.click(within(assetMenuButton).getByLabelText('asset-menu-toggle')); fireEvent.click(screen.getByText('Info')); + executeThunk(getUsagePaths({ + courseId, + asset: { id: 'mOckID1', displayName: 'mOckID1' }, + setSelectedRows: jest.fn(), + }), store.dispatch); expect(screen.getAllByLabelText('mOckID1')[0]).toBeVisible(); fireEvent.click(screen.getByLabelText('Checkbox')); executeThunk(updateAssetLock({ @@ -265,8 +281,9 @@ describe('FilesAndUploads', () => { locked: false, }), store.dispatch); }); - const saveStatus = store.getState().assets.savingStatus; - expect(saveStatus).toEqual(RequestStatus.SUCCESSFUL); + expect(screen.getByText(messages.usageNotInUseMessage.defaultMessage)).toBeVisible(); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('should unlock asset', async () => { renderComponent(); @@ -284,8 +301,8 @@ describe('FilesAndUploads', () => { locked: false, }), store.dispatch); }); - const saveStatus = store.getState().assets.savingStatus; - expect(saveStatus).toEqual(RequestStatus.SUCCESSFUL); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('should lock asset', async () => { renderComponent(); @@ -303,8 +320,8 @@ describe('FilesAndUploads', () => { locked: true, }), store.dispatch); }); - const saveStatus = store.getState().assets.savingStatus; - expect(saveStatus).toEqual(RequestStatus.SUCCESSFUL); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.SUCCESSFUL); }); it('delete button should delete file', async () => { renderComponent(); @@ -375,6 +392,25 @@ describe('FilesAndUploads', () => { expect(screen.getByTestId('grid-card-mOckID1')).toBeVisible(); expect(screen.getByText('Error')).toBeVisible(); }); + it('404 usage path fetch should show error', async () => { + renderComponent(); + await mockStore(RequestStatus.SUCCESSFUL); + expect(screen.getByTestId('grid-card-mOckID3')).toBeVisible(); + const assetMenuButton = screen.getByTestId('file-menu-dropdown-mOckID3'); + expect(assetMenuButton).toBeVisible(); + axiosMock.onGet(`${getAssetsUrl(courseId)}mOckID3/usage`).reply(404); + await waitFor(() => { + fireEvent.click(within(assetMenuButton).getByLabelText('asset-menu-toggle')); + fireEvent.click(screen.getByText('Info')); + executeThunk(getUsagePaths({ + courseId, + asset: { id: 'mOckID3', displayName: 'mOckID3' }, + setSelectedRows: jest.fn(), + }), store.dispatch); + }); + const { usageStatus } = store.getState().assets; + expect(usageStatus).toEqual(RequestStatus.FAILED); + }); it('404 lock update should show error', async () => { renderComponent(); await mockStore(RequestStatus.SUCCESSFUL); @@ -391,8 +427,8 @@ describe('FilesAndUploads', () => { locked: true, }), store.dispatch); }); - const saveStatus = store.getState().assets.savingStatus; - expect(saveStatus).toEqual(RequestStatus.FAILED); + const updateStatus = store.getState().assets.updatingStatus; + expect(updateStatus).toEqual(RequestStatus.FAILED); expect(screen.getByText('Error')).toBeVisible(); }); }); diff --git a/src/files-and-uploads/UsageMetricsMessage.jsx b/src/files-and-uploads/UsageMetricsMessage.jsx new file mode 100644 index 0000000000..cefb91ccde --- /dev/null +++ b/src/files-and-uploads/UsageMetricsMessage.jsx @@ -0,0 +1,60 @@ +import { injectIntl, intlShape, FormattedMessage } from '@edx/frontend-platform/i18n'; +import PropTypes from 'prop-types'; +import { Icon, Row, Spinner } from '@edx/paragon'; +import { ErrorOutline } from '@edx/paragon/icons'; +import isEmpty from 'lodash/isEmpty'; +import { RequestStatus } from '../data/constants'; +import messages from './messages'; + +const UsageMetricsMessage = ({ + usagePathStatus, + usageLocations, + error, + // injected + intl, +}) => { + let usageMessage; + if (usagePathStatus === RequestStatus.SUCCESSFUL) { + usageMessage = isEmpty(usageLocations) ? ( + + ) : ( +
    + {usageLocations.map((location) => (
  • {location}
  • ))} +
+ ); + } else if (usagePathStatus === RequestStatus.FAILED) { + usageMessage = ( + + + {intl.formatMessage(messages.errorAlertMessage, { message: error })} + + ); + } else { + usageMessage = ( + <> + + + + ); + } + return usageMessage; +}; + +UsageMetricsMessage.propTypes = { + usagePathStatus: PropTypes.string.isRequired, + usageLocations: PropTypes.arrayOf(PropTypes.string).isRequired, + error: PropTypes.arrayOf(PropTypes.string).isRequired, + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(UsageMetricsMessage); diff --git a/src/files-and-uploads/data/api.js b/src/files-and-uploads/data/api.js index d3601bbf2e..06009545de 100644 --- a/src/files-and-uploads/data/api.js +++ b/src/files-and-uploads/data/api.js @@ -20,6 +20,11 @@ export async function getAssets(courseId, totalCount) { .get(`${getAssetsUrl(courseId)}?page_size=${pageCount}`); return camelCaseObject(data); } +export async function getAssetUsagePaths({ courseId, assetId }) { + const { data } = await getAuthenticatedHttpClient() + .get(`${getAssetsUrl(courseId)}${assetId}/usage`); + return camelCaseObject(data); +} /** * Delete custom page for provided block. diff --git a/src/files-and-uploads/data/slice.js b/src/files-and-uploads/data/slice.js index 00d470b419..5f61d33564 100644 --- a/src/files-and-uploads/data/slice.js +++ b/src/files-and-uploads/data/slice.js @@ -8,13 +8,15 @@ const slice = createSlice({ initialState: { assetIds: [], loadingStatus: RequestStatus.IN_PROGRESS, - savingStatus: '', + updatingStatus: '', addingStatus: '', deletingStatus: '', + usageStatus: '', errors: { upload: [], delete: [], lock: [], + usageMetrics: [], }, totalCount: 0, }, @@ -28,8 +30,8 @@ const slice = createSlice({ updateLoadingStatus: (state, { payload }) => { state.loadingStatus = payload.status; }, - updateSavingStatus: (state, { payload }) => { - state.savingStatus = payload.status; + updateUpdatingStatus: (state, { payload }) => { + state.updatingStatus = payload.status; }, updateAddingStatus: (state, { payload }) => { state.addingStatus = payload.status; @@ -43,6 +45,9 @@ const slice = createSlice({ addAssetSuccess: (state, { payload }) => { state.assetIds = [payload.assetId, ...state.assetIds]; }, + updateUsageStatus: (state, { payload }) => { + state.usageStatus = payload.status; + }, updateErrors: (state, { payload }) => { const { error, message } = payload; const currentErrorState = state.errors[error]; @@ -55,11 +60,12 @@ export const { setAssetIds, setTotalCount, updateLoadingStatus, - updateSavingStatus, + updateUpdatingStatus, deleteAssetSuccess, updateDeletingStatus, addAssetSuccess, updateAddingStatus, + updateUsageStatus, updateErrors, } = slice.actions; diff --git a/src/files-and-uploads/data/thunks.js b/src/files-and-uploads/data/thunks.js index 6ef78b32ed..9039b8626f 100644 --- a/src/files-and-uploads/data/thunks.js +++ b/src/files-and-uploads/data/thunks.js @@ -7,6 +7,7 @@ import { } from '../../generic/model-store'; import { getAssets, + getAssetUsagePaths, addAsset, deleteAsset, updateLockStatus, @@ -15,12 +16,13 @@ import { setAssetIds, setTotalCount, updateLoadingStatus, - updateSavingStatus, + updateUpdatingStatus, deleteAssetSuccess, updateDeletingStatus, addAssetSuccess, updateAddingStatus, updateErrors, + updateUsageStatus, } from './slice'; import { updateFileValues } from './utils'; @@ -104,7 +106,7 @@ export function addAssetFile(courseId, file, totalCount) { export function updateAssetLock({ assetId, courseId, locked }) { return async (dispatch) => { - dispatch(updateSavingStatus({ status: RequestStatus.IN_PROGRESS })); + dispatch(updateUpdatingStatus({ status: RequestStatus.IN_PROGRESS })); try { await updateLockStatus({ assetId, courseId, locked }); @@ -115,11 +117,26 @@ export function updateAssetLock({ assetId, courseId, locked }) { locked, }, })); - dispatch(updateSavingStatus({ status: RequestStatus.SUCCESSFUL })); + dispatch(updateUpdatingStatus({ status: RequestStatus.SUCCESSFUL })); } catch (error) { const lockStatus = locked ? 'lock' : 'unlock'; dispatch(updateErrors({ error: 'lock', message: `Failed to ${lockStatus} file id ${assetId}.` })); - dispatch(updateSavingStatus({ status: RequestStatus.FAILED })); + dispatch(updateUpdatingStatus({ status: RequestStatus.FAILED })); + } + }; +} + +export function getUsagePaths({ asset, courseId, setSelectedRows }) { + return async (dispatch) => { + dispatch(updateUsageStatus({ status: RequestStatus.IN_PROGRESS })); + + try { + const { usageLocations } = await getAssetUsagePaths({ assetId: asset.id, courseId }); + setSelectedRows([{ original: { ...asset, usageLocations } }]); + dispatch(updateUsageStatus({ status: RequestStatus.SUCCESSFUL })); + } catch (error) { + dispatch(updateErrors({ error: 'usageMetrics', message: `Failed to get usage metrics for ${asset.displayName}.` })); + dispatch(updateUsageStatus({ status: RequestStatus.FAILED })); } }; } diff --git a/src/files-and-uploads/data/utils.js b/src/files-and-uploads/data/utils.js index 823a81f6cb..75ace4caff 100644 --- a/src/files-and-uploads/data/utils.js +++ b/src/files-and-uploads/data/utils.js @@ -24,7 +24,12 @@ export const updateFileValues = (files) => { const utcDateString = dateAdded.replace(/\bat\b/g, ''); const utcDateTime = new Date(utcDateString).toString(); - updatedFiles.push({ ...file, wrapperType, dateAdded: utcDateTime }); + updatedFiles.push({ + ...file, + wrapperType, + dateAdded: utcDateTime, + usageLocations: [], + }); }); return updatedFiles; @@ -46,6 +51,23 @@ export const getSrc = ({ thumbnail, wrapperType, externalUrl }) => { } }; +export const getFileSizeToClosestByte = (fileSize, numberOfDivides = 0) => { + if (fileSize > 1000) { + const updatedSize = fileSize / 1000; + const incrementNumberOfDivides = numberOfDivides + 1; + return getFileSizeToClosestByte(updatedSize, incrementNumberOfDivides); + } + const fileSizeFixedDecimal = Number.parseFloat(fileSize).toFixed(2); + switch (numberOfDivides) { + case 1: + return `${fileSizeFixedDecimal} KB`; + case 2: + return `${fileSizeFixedDecimal} MB`; + default: + return `${fileSizeFixedDecimal} B`; + } +}; + export const sortFiles = (files, sortType) => { const [sort, direction] = sortType.split(','); let sortedFiles; diff --git a/src/files-and-uploads/data/utils.test.js b/src/files-and-uploads/data/utils.test.js new file mode 100644 index 0000000000..ebe9a38ebb --- /dev/null +++ b/src/files-and-uploads/data/utils.test.js @@ -0,0 +1,21 @@ +import { getFileSizeToClosestByte } from './utils'; + +describe('FilesAndUploads utils', () => { + describe('getFileSizeToClosestByte', () => { + it('should return file size with B for bytes', () => { + const expectedSize = '219.00 B'; + const actualSize = getFileSizeToClosestByte(219); + expect(expectedSize).toEqual(actualSize); + }); + it('should return file size with KB for kilobytes', () => { + const expectedSize = '21.90 KB'; + const actualSize = getFileSizeToClosestByte(21900); + expect(expectedSize).toEqual(actualSize); + }); + it('should return file size with MB for megabytes', () => { + const expectedSize = '2.19 MB'; + const actualSize = getFileSizeToClosestByte(2190000); + expect(expectedSize).toEqual(actualSize); + }); + }); +}); diff --git a/src/files-and-uploads/factories/mockApiResponses.jsx b/src/files-and-uploads/factories/mockApiResponses.jsx index d3ceb27209..3437fd0fcc 100644 --- a/src/files-and-uploads/factories/mockApiResponses.jsx +++ b/src/files-and-uploads/factories/mockApiResponses.jsx @@ -10,13 +10,15 @@ export const initialState = { assets: { assetIds: ['mOckID1'], loadingStatus: 'successful', - savingStatus: '', + updatingStatus: '', deletingStatus: '', addingStatus: '', + usageStatus: '', errors: { upload: [], delete: [], lock: [], + usageMetrics: [], }, }, models: { @@ -31,6 +33,7 @@ export const initialState = { wrapperType: 'document', dateAdded: '', thumbnail: null, + fileSize: 1234567, }, }, }, @@ -47,6 +50,7 @@ export const generateFetchAssetApiResponse = () => ({ contentType: 'image/png', dateAdded: '', thumbnail: '/asset', + fileSize: 123, }, { id: 'mOckID5', @@ -67,6 +71,7 @@ export const generateFetchAssetApiResponse = () => ({ contentType: 'application/pdf', dateAdded: 'Aug 17, 2023 at 22:08 UTC', thumbnail: null, + fileSize: 1234, }, { id: 'mOckID4', @@ -87,6 +92,7 @@ export const generateFetchAssetApiResponse = () => ({ contentType: 'application/octet-stream', dateAdded: '', thumbnail: null, + fileSize: 0, }, { id: 'mOckID6-2', @@ -118,6 +124,7 @@ export const generateNewAssetApiResponse = () => ({ thumbnail: '/download.png', locked: false, id: 'mOckID2', + fileSize: 1234, }, }); diff --git a/src/files-and-uploads/messages.js b/src/files-and-uploads/messages.js index 4af9b26f2b..7eed7d54a8 100644 --- a/src/files-and-uploads/messages.js +++ b/src/files-and-uploads/messages.js @@ -42,27 +42,27 @@ const messages = defineMessages({ defaultMessage: '{message}', }, dateAddedTitle: { - id: 'course-authoring.files-and-uploads.dateAdded.title', + id: 'course-authoring.files-and-uploads.file-info.dateAdded.title', defaultMessage: 'Date added', }, fileSizeTitle: { - id: 'course-authoring.files-and-uploads.fileSize.title', + id: 'course-authoring.files-and-uploads.file-info.fileSize.title', defaultMessage: 'File size', }, studioUrlTitle: { - id: 'course-authoring.files-and-uploads.studioUrl.title', + id: 'course-authoring.files-and-uploads.file-info.studioUrl.title', defaultMessage: 'Studio URL', }, webUrlTitle: { - id: 'course-authoring.files-and-uploads.webUrl.title', + id: 'course-authoring.files-and-uploads.file-info.webUrl.title', defaultMessage: 'Web URL', }, lockFileTitle: { - id: 'course-authoring.files-and-uploads.lockFile.title', + id: 'course-authoring.files-and-uploads.file-info.lockFile.title', defaultMessage: 'Lock file', }, lockFileTooltipContent: { - id: 'course-authoring.files-and-uploads.lockFile.tooltip.content', + id: 'course-authoring.files-and-uploads.file-info.lockFile.tooltip.content', defaultMessage: `By default, anyone can access a file you upload if they know the web URL, even if they are not enrolled in your course. You can prevent outside access to a file by locking the file. When @@ -70,9 +70,17 @@ const messages = defineMessages({ in your course and signed in to access the file.`, }, usageTitle: { - id: 'course-authoring.files-and-uploads.usage.title', + id: 'course-authoring.files-and-uploads.file-info.usage.title', defaultMessage: 'Usage', }, + usageLoadingMessage: { + id: 'course-authoring.files-and-uploads.file-info.usage.loading.message', + defaultMessage: 'Loading', + }, + usageNotInUseMessage: { + id: 'course-authoring.files-and-uploads.file-info.usage.notInUse.message', + defaultMessage: 'Currently not in use', + }, copyStudioUrlTitle: { id: 'course-authoring.files-and-uploads.cardMenu.copyStudioUrlTitle', defaultMessage: 'Copy Studio Url', diff --git a/src/files-and-uploads/table-components/GalleryCard.jsx b/src/files-and-uploads/table-components/GalleryCard.jsx index 57cd1c277c..dce4183812 100644 --- a/src/files-and-uploads/table-components/GalleryCard.jsx +++ b/src/files-and-uploads/table-components/GalleryCard.jsx @@ -4,7 +4,6 @@ import { ActionRow, Icon, Card, - useToggle, Chip, Truncate, Image, @@ -13,7 +12,6 @@ import { MoreVert, } from '@edx/paragon/icons'; import FileMenu from '../FileMenu'; -import FileInfo from '../FileInfo'; import { getSrc } from '../data/utils'; const GalleryCard = ({ @@ -21,8 +19,8 @@ const GalleryCard = ({ original, handleLockedAsset, handleOpenDeleteConfirmation, + handleOpenAssetInfo, }) => { - const [isAssetInfoOpen, openAssetInfo, closeAssetinfo] = useToggle(false); const lockAsset = () => { const { locked } = original; handleLockedAsset(original.id, !locked); @@ -33,53 +31,45 @@ const GalleryCard = ({ }); return ( - <> - - - handleOpenDeleteConfirmation([{ original }])} - /> - - )} - /> - -
- {original.thumbnail ? ( - - ) : ( -
- -
- )} -
-
- - {original.displayName} - -
-
- - - {original.wrapperType} - - -
- + + handleOpenAssetInfo(original)} + portableUrl={original.portableUrl} + iconSrc={MoreVert} + id={original.id} + openDeleteConfirmation={() => handleOpenDeleteConfirmation([{ original }])} + /> + + )} /> - + +
+ {original.thumbnail ? ( + + ) : ( +
+ +
+ )} +
+
+ + {original.displayName} + +
+
+ + + {original.wrapperType} + + + ); }; @@ -99,6 +89,7 @@ GalleryCard.propTypes = { }).isRequired, handleLockedAsset: PropTypes.func.isRequired, handleOpenDeleteConfirmation: PropTypes.func.isRequired, + handleOpenAssetInfo: PropTypes.func.isRequired, }; export default GalleryCard; diff --git a/src/files-and-uploads/table-components/ListCard.jsx b/src/files-and-uploads/table-components/ListCard.jsx index 495741a2a7..639061c946 100644 --- a/src/files-and-uploads/table-components/ListCard.jsx +++ b/src/files-and-uploads/table-components/ListCard.jsx @@ -4,7 +4,6 @@ import { ActionRow, Icon, Card, - useToggle, Chip, Truncate, Image, @@ -13,7 +12,6 @@ import { MoreVert, } from '@edx/paragon/icons'; import FileMenu from '../FileMenu'; -import FileInfo from '../FileInfo'; import { getSrc } from '../data/utils'; const ListCard = ({ @@ -21,8 +19,8 @@ const ListCard = ({ original, handleLockedAsset, handleOpenDeleteConfirmation, + handleOpenAssetInfo, }) => { - const [isAssetInfoOpen, openAssetInfo, closeAssetinfo] = useToggle(false); const lockAsset = () => { const { locked } = original; handleLockedAsset(original.id, !locked); @@ -33,55 +31,47 @@ const ListCard = ({ }); return ( - <> - -
- {original.thumbnail ? ( - - ) : ( -
- -
- )} -
- - -
- - {original.displayName} - -
- - {original.wrapperType} - -
-
- - - handleOpenDeleteConfirmation([{ original }])} - /> - - -
- - + +
+ {original.thumbnail ? ( + + ) : ( +
+ +
+ )} +
+ + +
+ + {original.displayName} + +
+ + {original.wrapperType} + +
+
+ + + handleOpenAssetInfo(original)} + portableUrl={original.portableUrl} + iconSrc={MoreVert} + id={original.id} + openDeleteConfirmation={() => handleOpenDeleteConfirmation([{ original }])} + /> + + +
); }; @@ -101,6 +91,7 @@ ListCard.propTypes = { }).isRequired, handleLockedAsset: PropTypes.func.isRequired, handleOpenDeleteConfirmation: PropTypes.func.isRequired, + handleOpenAssetInfo: PropTypes.func.isRequired, }; export default ListCard;