Skip to content

Commit

Permalink
feat: support uploading assets
Browse files Browse the repository at this point in the history
  • Loading branch information
bradenmacdonald committed Oct 23, 2024
1 parent a8aa495 commit 1c6fd2a
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 12 deletions.
70 changes: 70 additions & 0 deletions src/library-authoring/component-info/ComponentAdvancedAssets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* eslint-disable no-nested-ternary */
/* eslint-disable import/prefer-default-export */
import React from 'react';
import {
Dropzone,
} from '@openedx/paragon';
import { Plus } 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 { useLibraryContext } from '../common/context';
import { getXBlockAssetsApiUrl } from '../data/api';
import { useInvalidateXBlockAssets, useXBlockAssets } from '../data/apiHooks';
import messages from './messages';

export const ComponentAdvancedAssets: React.FC<Record<never, never>> = () => {
const intl = useIntl();
const { readOnly, sidebarComponentInfo } = useLibraryContext();

const usageKey = sidebarComponentInfo?.id;
// istanbul ignore if: this should never happen in production
if (!usageKey) {
throw new Error('sidebarComponentUsageKey is required to render ComponentAdvancedAssets');
}

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

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 client = getAuthenticatedHttpClient();
try {
await client.put(uploadUrl, uploadData, requestConfig);
} catch (error) {
handleError(error);
return;
}
refreshAssets();
}, [usageKey]);

return (
<>
<ul>
{ areAssetsLoading ? <li><LoadingSpinner /></li> : null }
{ assets?.map(a => (
<li key={a.path}>
<a href={a.url}>{a.path}</a>{' '}
(<FormattedNumber value={a.size} notation="compact" unit="byte" unitDisplay="narrow" />)
</li>
)) }
</ul>
{ assets !== undefined && !readOnly // Wait until assets have loaded before displaying add button:
? (
<Dropzone
style={{ height: '200px' }}
onProcessUpload={handleProcessUpload}
onUploadProgress={() => {}}
/>
)
: null }
</>
);
};
15 changes: 3 additions & 12 deletions src/library-authoring/component-info/ComponentAdvancedInfo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import {
OverlayTrigger,
Tooltip,
} from '@openedx/paragon';
import { FormattedMessage, FormattedNumber, useIntl } from '@edx/frontend-platform/i18n';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';

import { LoadingSpinner } from '../../generic/Loading';
import { CodeEditor, EditorAccessor } from '../../generic/CodeEditor';
import { useLibraryContext } from '../common/context';
import {
useUpdateXBlockOLX,
useXBlockAssets,
useXBlockOLX,
} from '../data/apiHooks';
import messages from './messages';
import { ComponentAdvancedAssets } from './ComponentAdvancedAssets';

const ComponentAdvancedInfoInner: React.FC<Record<never, never>> = () => {
const intl = useIntl();
Expand All @@ -31,7 +31,6 @@ const ComponentAdvancedInfoInner: React.FC<Record<never, never>> = () => {
}

const { data: olx, isLoading: isOLXLoading } = useXBlockOLX(usageKey);
const { data: assets, isLoading: areAssetsLoading } = useXBlockAssets(usageKey);
const editorRef = React.useRef<EditorAccessor | undefined>(undefined);
const [isEditingOLX, setEditingOLX] = React.useState(false);
const olxUpdater = useUpdateXBlockOLX(usageKey);
Expand Down Expand Up @@ -101,15 +100,7 @@ const ComponentAdvancedInfoInner: React.FC<Record<never, never>> = () => {
);
})()}
<h3 className="h5"><FormattedMessage {...messages.advancedDetailsAssets} /></h3>
<ul>
{ areAssetsLoading ? <li><LoadingSpinner /></li> : null }
{ assets?.map(a => (
<li key={a.path}>
<a href={a.url}>{a.path}</a>{' '}
(<FormattedNumber value={a.size} notation="compact" unit="byte" unitDisplay="narrow" />)
</li>
)) }
</ul>
<ComponentAdvancedAssets />
</>
);
};
Expand Down
9 changes: 9 additions & 0 deletions src/library-authoring/data/apiHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
type Query,
type QueryClient,
} from '@tanstack/react-query';
import { useCallback } from 'react';

import { getLibraryId } from '../../generic/key-utils';
import {
Expand Down Expand Up @@ -397,6 +398,14 @@ export const useXBlockAssets = (usageKey: string) => (
})
);

/** Refresh the list of assets (static files) attached to a library component */
export const useInvalidateXBlockAssets = (usageKey: string) => {
const client = useQueryClient();
return useCallback(() => {
client.invalidateQueries({ queryKey: xblockQueryKeys.xblockAssets(usageKey) });
}, [usageKey]);
};

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

0 comments on commit 1c6fd2a

Please sign in to comment.