Skip to content

Commit

Permalink
feat: support deleting assets
Browse files Browse the repository at this point in the history
  • Loading branch information
bradenmacdonald committed Oct 23, 2024
1 parent 1c6fd2a commit d010d55
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 3 deletions.
31 changes: 28 additions & 3 deletions src/library-authoring/component-info/ComponentAdvancedAssets.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
/* eslint-disable import/prefer-default-export */
import React from 'react';
import {
Button,
Dropzone,
} from '@openedx/paragon';
import { Plus } from '@openedx/paragon/icons';
import { Delete } from '@openedx/paragon/icons';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import { FormattedMessage, FormattedNumber, useIntl } from '@edx/frontend-platform/i18n';

import { LoadingSpinner } from '../../generic/Loading';
import DeleteModal from '../../generic/delete-modal/DeleteModal';
import { useLibraryContext } from '../common/context';
import { getXBlockAssetsApiUrl } from '../data/api';
import { useInvalidateXBlockAssets, useXBlockAssets } from '../data/apiHooks';
import { useDeleteXBlockAsset, useInvalidateXBlockAssets, useXBlockAssets } from '../data/apiHooks';
import messages from './messages';

export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
Expand All @@ -24,17 +26,19 @@ export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
throw new Error('sidebarComponentUsageKey is required to render ComponentAdvancedAssets');
}

// For listing assets:
const { data: assets, isLoading: areAssetsLoading } = useXBlockAssets(usageKey);
const refreshAssets = useInvalidateXBlockAssets(usageKey);

// For uploading assets:
const handleProcessUpload = React.useCallback(async ({
fileData, requestConfig, handleError,
}: { fileData: FormData, requestConfig: any, handleError: any }) => {
const uploadData = new FormData();
const file = fileData.get('file') as File;
uploadData.set('content', file); // Paragon calls this 'file' but our API needs it called 'content'
// TODO: make the filename unique if is already exists in assets list, to avoid overwriting.
const uploadUrl = getXBlockAssetsApiUrl(usageKey) + encodeURI(file.name);
const uploadUrl = `${getXBlockAssetsApiUrl(usageKey)}static/${encodeURI(file.name)}`;
const client = getAuthenticatedHttpClient();
try {
await client.put(uploadUrl, uploadData, requestConfig);
Expand All @@ -45,6 +49,14 @@ export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
refreshAssets();
}, [usageKey]);

// For deleting assets:
const deleter = useDeleteXBlockAsset(usageKey);
const [filePathToDelete, setConfirmDeleteAsset] = React.useState<string>('');
const deleteFile = React.useCallback(() => {
deleter.mutateAsync(filePathToDelete); // Don't wait for this before clearing the modal on the next line
setConfirmDeleteAsset('');
}, [filePathToDelete, usageKey]);

return (
<>
<ul>
Expand All @@ -53,6 +65,9 @@ export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
<li key={a.path}>
<a href={a.url}>{a.path}</a>{' '}
(<FormattedNumber value={a.size} notation="compact" unit="byte" unitDisplay="narrow" />)
<Button variant="link" size="sm" iconBefore={Delete} onClick={() => { setConfirmDeleteAsset(a.path); }} title={intl.formatMessage(messages.advancedDetailsAssetsDeleteButton)}>
<span className="sr-only"><FormattedMessage {...messages.advancedDetailsAssetsDeleteButton} /></span>
</Button>
</li>
)) }
</ul>
Expand All @@ -65,6 +80,16 @@ export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
/>
)
: null }

<DeleteModal
isOpen={filePathToDelete !== ''}
close={() => { setConfirmDeleteAsset(''); }}
variant="warning"
title={intl.formatMessage(messages.advancedDetailsAssetsDeleteFileTitle)}
description={`Are you sure you want to delete ${filePathToDelete}?`}
onDeleteSubmit={deleteFile}
btnState="default"
/>
</>
);
};
10 changes: 10 additions & 0 deletions src/library-authoring/component-info/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ const messages = defineMessages({
defaultMessage: 'Assets (Files)',
description: 'Heading for files attached to the component',
},
advancedDetailsAssetsDeleteFileTitle: {
id: 'course-authoring.library-authoring.component.advanced.assets.delete-file-title',
defaultMessage: 'Delete File',
description: 'Title for confirmation dialog when deleting a file',
},
advancedDetailsAssetsDeleteButton: {
id: 'course-authoring.library-authoring.component.advanced.assets.delete-btn',
defaultMessage: 'Delete this file',
description: 'screen reader description of the delete button for each static asset file',
},
advancedDetailsOLX: {
id: 'course-authoring.library-authoring.component.advanced.olx',
defaultMessage: 'OLX Source',
Expand Down
8 changes: 8 additions & 0 deletions src/library-authoring/data/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,14 @@ export async function getXBlockAssets(usageKey: string): Promise<{ path: string;
return data.files;
}

/**
* Delete a single asset file
*/
// istanbul ignore next
export async function deleteXBlockAsset(usageKey: string, path: string): Promise<void> {
await getAuthenticatedHttpClient().delete(getXBlockAssetsApiUrl(usageKey) + encodeURIComponent(path));
}

/**
* Get the collection metadata.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/library-authoring/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
updateComponentCollections,
removeComponentsFromCollection,
publishXBlock,
deleteXBlockAsset,
} from './api';

export const libraryQueryPredicate = (query: Query, libraryId: string): boolean => {
Expand Down Expand Up @@ -406,6 +407,19 @@ export const useInvalidateXBlockAssets = (usageKey: string) => {
}, [usageKey]);
};

/**
* Use this mutation to delete an asset file from a library
*/
export const useDeleteXBlockAsset = (usageKey: string) => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (path: string) => deleteXBlockAsset(usageKey, path),
onSettled: () => {
queryClient.invalidateQueries({ queryKey: xblockQueryKeys.xblockAssets(usageKey) });
},
});
};

/**
* Get the metadata for a collection in a library
*/
Expand Down

0 comments on commit d010d55

Please sign in to comment.