Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/editors/containers/PdfEditor/api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useQuery } from '@tanstack/react-query';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useSelector } from 'react-redux';
import { selectors } from '@src/editors/data/redux';
import { camelizeKeys } from '@src/editors/utils';
Expand Down Expand Up @@ -72,3 +72,24 @@ export const useBlockHandlerData = <T>({
},
});
};

export const usePdfConversion = (blockId: string) => {
const studioEndpointUrl = useSelector(selectors.app.studioEndpointUrl)!;
const isLibrary = useSelector(selectors.app.isLibrary);
const client = getAuthenticatedHttpClient();
return useMutation({
mutationFn: async (url: string) => {
const result = await client.post(
await deriveHandlerUrl({
blockId,
studioEndpointUrl,
handlerName: 'convert_pdf',
isLibrary,
client,
}),
{ url },
);
return result.data.url as string;
},
});
};
34 changes: 32 additions & 2 deletions src/editors/containers/PdfEditor/components/PdfEditingModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { UploadWidget } from '@src/editors/sharedComponents/UploadWidget';
import { Spinner } from '@openedx/paragon';
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { FieldSaverArgs } from '@src/editors/sharedComponents/UploadWidget/UploadWidget';
import { usePdfConversion } from '@src/editors/containers/PdfEditor/api';

const EditorWrapper: React.FC<PropsWithChildren> = ({ children }) => {
const intl = useIntl();
Expand Down Expand Up @@ -44,6 +46,7 @@ const PdfEditingModal: React.FC<EditorComponent> = (props) => {
const { fields, blockId, isLibrary } = useContext(PdfBlockContext);
const originalState = useRef({ ...fields });
const { values, setValues } = useFormikContext<PdfState>();
const mutation = usePdfConversion(blockId);

useEffect(() => {
// Form is initialized before we get these values, so we have to set them
Expand All @@ -57,19 +60,46 @@ const PdfEditingModal: React.FC<EditorComponent> = (props) => {
const settings = { ...values };
// disableAllDownload is not a setting we control, but a backend flag. Have to remove it or the
// backend will reject.
return Object.fromEntries(Object.entries(settings).filter(([key]) => key !== 'disableAllDownload'));
const ignored = ['disableAllDownload', 'conversionAvailable'];
return Object.fromEntries(Object.entries(settings).filter(([key]) => !ignored.includes(key)));
};

const supportedFormats = ['application/pdf'];
if (values.conversionAvailable) {
supportedFormats.push(
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.text',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/rtf',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
);
}

const saver = async (args: FieldSaverArgs<string>) => {
if (!values.conversionAvailable || args.sourceFile.type == 'application/pdf') {
// No conversion means no swapping of file values-- just set the field value and be done.
return args.control.setValue(args.value);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone uploads a .docx, lets it convert, then replaces it with a plain .pdf, this branch only sets the url field. sourceUrl keeps pointing at the old .docx, so learners get a "Download the source document" link to a file that has nothing to do with the PDF they are looking at. Probably worth clearing sourceUrl on this path, at least when the current value was one this component set itself.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think keeping it is the lesser of the two evils here. If the user has the PDF autoconverted, and doesn't like the result, they might go into name-brand Office and print to PDF, and then replace the PDF, not knowing that it implicitly cleared the source URL. I'm going to lean toward the case that prevents data loss even if it might mean that the two documents get a bit out of sync.

In most cases I would not expect the replacement document to be totally unrelated, just out of date.

}
const pdfUrl = await mutation.mutateAsync(args.value);
await setValues(prev => ({ ...prev, url: pdfUrl, sourceUrl: args.value }));
};

return (
<EditorContainer {...props} isDirty={isDirty} getContent={getContent}>
<EditorWrapper>
<div className="mt-2">
<UploadWidget
supportedFileFormats="application/pdf"
supportedFileFormats={supportedFormats}
urlFieldName="url"
label={intl.formatMessage(messages.urlFieldLabel)}
blockId={blockId}
isLibrary={isLibrary}
saveField={saver}
id="pdf-url"
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react';
import React, { useContext } from 'react';
import { useFormikContext } from 'formik';
import { PdfState } from '@src/editors/containers/PdfEditor/contexts';
import { PdfBlockContext, PdfState } from '@src/editors/containers/PdfEditor/contexts';
import { optional, useUrlValidator } from '@src/editors/utils/validators';
import { useIntl } from '@edx/frontend-platform/i18n';
import CheckboxField from '@src/editors/sharedComponents/CheckboxField';
Expand All @@ -10,7 +10,8 @@ import messages from './messages';
const DownloadOptions: React.FC = () => {
const intl = useIntl();
const { values } = useFormikContext<PdfState>();
const urlValidator = optional(useUrlValidator());
const { isLibrary } = useContext(PdfBlockContext);
const urlValidator = optional(useUrlValidator({ allowAbsolute: isLibrary }));
if (values.disableAllDownload) {
// Download configuration is disabled at the instance-level, so don't even show these options.
return <></>; // eslint-disable-line react/jsx-no-useless-fragment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ const messages = defineMessages({
},
sourceUrlHint: {
id: 'authoring.pdfEditor.formGroups.downloadOptions.sourceUrl.hint',
defaultMessage: 'Add a link to the original or editable file (e.g. Word or PowerPoint). Appears as a separate link.',
defaultMessage: 'Add a link to the original or editable file (e.g. Word or PowerPoint). Appears as a separate '
+ 'link. You are encouraged to provide this link when auto-generating PDFs, and to ensure this source document '
+ 'conforms to accessibility standards.',
description: 'Hint for the field used to specify the URL of a source document a PDF was generated from.',
},
});
Expand Down
5 changes: 4 additions & 1 deletion src/editors/containers/PdfEditor/contexts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ export interface PdfState {
allowDownload: boolean;
sourceText: string;
sourceUrl: string;
// Note: Not a field, so can't be set.
// Note: The following are not fields, so can't be set.
// They're indicators of backend settings.
disableAllDownload: boolean;
conversionAvailable: boolean;
}

declare interface PdfBlockContextInterface {
Expand All @@ -35,6 +37,7 @@ export const initialPdfState: () => PdfState = () => ({
sourceText: '',
sourceUrl: '',
disableAllDownload: false,
conversionAvailable: false,
});

export const PdfBlockContext = createContext<PdfBlockContextInterface>({
Expand Down
Loading