Merge pull request #18518 from GuiLeme/issue-18449

feat: Enable download of presentation with its original format
This commit is contained in:
Anton Georgiev 2023-08-22 21:29:43 -04:00 committed by GitHub
commit c29000cdc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 237 additions and 185 deletions

View File

@ -41,9 +41,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
BbbCommonEnvCoreMsg(envelope, event)
}
def buildNewPresFileAvailable(fileURI: String, presId: String, typeOfExport: String): NewPresFileAvailableMsg = {
def buildNewPresFileAvailable(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
presId: String, fileStateType: String): NewPresFileAvailableMsg = {
val header = BbbClientMsgHeader(NewPresFileAvailableMsg.NAME, "not-used", "not-used")
val body = NewPresFileAvailableMsgBody(fileURI, presId, typeOfExport)
val body = NewPresFileAvailableMsgBody(annotatedFileURI, originalFileURI, convertedFileURI, presId, fileStateType)
NewPresFileAvailableMsg(header, body)
}
@ -52,8 +53,12 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, "not-used")
val envelope = BbbCoreEnvelope(PresentationPageConvertedEventMsg.NAME, routing)
val header = BbbClientMsgHeader(NewPresFileAvailableEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = NewPresFileAvailableEvtMsgBody(fileURI = newPresFileAvailableMsg.body.fileURI, presId = newPresFileAvailableMsg.body.presId,
typeOfExport = newPresFileAvailableMsg.body.typeOfExport)
val body = NewPresFileAvailableEvtMsgBody(
annotatedFileURI = newPresFileAvailableMsg.body.annotatedFileURI,
originalFileURI = newPresFileAvailableMsg.body.originalFileURI,
convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI, presId = newPresFileAvailableMsg.body.presId,
fileStateType = newPresFileAvailableMsg.body.fileStateType
)
val event = NewPresFileAvailableEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
@ -146,9 +151,9 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
val isOriginalPresentationType = m.body.typeOfExport == "Original"
val isPresentationOriginalOrConverted = m.body.fileStateType == "Original" || m.body.fileStateType == "Converted"
if (!isOriginalPresentationType) {
if (!isPresentationOriginalOrConverted) {
// Send Export Job to Redis
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
bus.outGW.send(job)
@ -159,13 +164,18 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
} else {
// Return existing uploaded file directly
val convertedFileName = currentPres.get.filenameConverted
val filename = if (convertedFileName == "") currentPres.get.name else convertedFileName
val presFilenameExt = filename.split("\\.").last
val originalFilename = currentPres.get.name
val originalFileExt = originalFilename.split("\\.").last
val convertedFileExt = if (convertedFileName != "") convertedFileName.split("\\.").last else ""
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, "DEFAULT_PRESENTATION_POD", "not-used", presId, true, filename)
val convertedFileURI = if (convertedFileName != "") List("presentation", "download", meetingId,
s"${presId}?presFilename=${presId}.${convertedFileExt}&filename=${convertedFileName}").mkString("", File.separator, "")
else ""
val originalFileURI = List("presentation", "download", meetingId,
s"${presId}?presFilename=${presId}.${originalFileExt}&filename=${originalFilename}").mkString("", File.separator, "")
val fileURI = List("presentation", "download", meetingId, s"${presId}?presFilename=${presId}.${presFilenameExt}&filename=${filename}").mkString("", File.separator, "")
val event = buildNewPresFileAvailable(fileURI, presId, m.body.typeOfExport)
val event = buildNewPresFileAvailable("", originalFileURI, convertedFileURI, presId,
m.body.fileStateType)
handle(event, liveMeeting, bus)
}
@ -224,7 +234,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
}
def handle(m: NewPresFileAvailableMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.info("Received NewPresFileAvailableMsg meetingId={} presId={} fileUrl={}", liveMeeting.props.meetingProp.intId, m.body.presId, m.body.fileURI)
log.info(
"Received NewPresFileAvailableMsg meetingId={} presId={}",
liveMeeting.props.meetingProp.intId, m.body.presId
)
bus.outGW.send(buildBroadcastNewPresFileAvailable(m, liveMeeting))
}

View File

@ -33,6 +33,8 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
msg.body.code,
presVO,
)
val originalDownloadableExtension = pres.name.split("\\.").last
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(
bus,
meetingId,
@ -40,7 +42,8 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
msg.header.userId,
pres.id,
pres.downloadable,
pres.name
pres.name,
originalDownloadableExtension
)
val presWithConvertedName = PresentationInPod(pres.id, pres.name, pres.current, pres.pages,

View File

@ -8,10 +8,12 @@ object PresentationSender {
def broadcastSetPresentationDownloadableEvtMsg(
bus: MessageBus,
meetingId: String,
podId: String, userId: String,
podId: String,
userId: String,
presentationId: String,
downloadable: Boolean,
presFilename: String,
downloadableExtension: String
): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
@ -20,12 +22,11 @@ object PresentationSender {
val envelope = BbbCoreEnvelope(SetPresentationDownloadableEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SetPresentationDownloadableEvtMsg.NAME, meetingId, userId)
val body = SetPresentationDownloadableEvtMsgBody(podId, presentationId, downloadable, presFilename)
val body = SetPresentationDownloadableEvtMsgBody(podId, presentationId, downloadable, presFilename, downloadableExtension)
val event = SetPresentationDownloadableEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
def broadcastPresentationConversionCompletedEvtMsg(
bus: MessageBus,
meetingId: String,

View File

@ -31,8 +31,11 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
pod <- PresentationPodsApp.getPresentationPod(state, podId)
pres <- pod.getPresentation(presentationId)
} yield {
val downloadableExtension = if (msg.body.fileStateType == "Original")
pres.name.split("\\.").last else pres.filenameConverted.split("\\.").last
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, pod.id,
msg.header.userId, presentationId, downloadable, pres.name)
msg.header.userId, presentationId, downloadable, pres.name, downloadableExtension)
val pods = state.presentationPodManager.setPresentationDownloadableInPod(pod.id, presentationId, downloadable)
state.update(pods)

View File

@ -12,11 +12,12 @@ case class PreuploadedPresentationsSysPubMsgBody(presentations: Vector[Presentat
object MakePresentationDownloadReqMsg { val NAME = "MakePresentationDownloadReqMsg" }
case class MakePresentationDownloadReqMsg(header: BbbClientMsgHeader, body: MakePresentationDownloadReqMsgBody) extends StandardMsg
case class MakePresentationDownloadReqMsgBody(presId: String, allPages: Boolean, pages: List[Int], typeOfExport: String)
case class MakePresentationDownloadReqMsgBody(presId: String, allPages: Boolean, pages: List[Int], fileStateType: String)
object NewPresFileAvailableMsg { val NAME = "NewPresFileAvailableMsg" }
case class NewPresFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableMsgBody) extends StandardMsg
case class NewPresFileAvailableMsgBody(fileURI: String, presId: String, typeOfExport: String)
case class NewPresFileAvailableMsgBody(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
presId: String, fileStateType: String)
object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" }
case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg
@ -39,7 +40,8 @@ case class NewPresentationEvtMsgBody(presentation: PresentationVO)
object NewPresFileAvailableEvtMsg { val NAME = "NewPresFileAvailableEvtMsg" }
case class NewPresFileAvailableEvtMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableEvtMsgBody) extends BbbCoreMsg
case class NewPresFileAvailableEvtMsgBody(fileURI: String, presId: String, typeOfExport: String)
case class NewPresFileAvailableEvtMsgBody(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
presId: String, fileStateType: String)
object PresAnnStatusEvtMsg { val NAME = "PresAnnStatusEvtMsg" }
case class PresAnnStatusEvtMsg(header: BbbClientMsgHeader, body: PresAnnStatusEvtMsgBody) extends BbbCoreMsg

View File

@ -33,7 +33,8 @@ case class RemovePresentationPubMsgBody(podId: String, presentationId: String)
object SetPresentationDownloadablePubMsg { val NAME = "SetPresentationDownloadablePubMsg" }
case class SetPresentationDownloadablePubMsg(header: BbbClientMsgHeader, body: SetPresentationDownloadablePubMsgBody) extends StandardMsg
case class SetPresentationDownloadablePubMsgBody(podId: String, presentationId: String, downloadable: Boolean)
case class SetPresentationDownloadablePubMsgBody(podId: String, presentationId: String, downloadable: Boolean,
fileStateType: String)
object ResizeAndMovePagePubMsg { val NAME = "ResizeAndMovePagePubMsg" }
case class ResizeAndMovePagePubMsg(header: BbbClientMsgHeader, body: ResizeAndMovePagePubMsgBody) extends StandardMsg
@ -331,7 +332,8 @@ case class RemovePresentationEvtMsgBody(podId: String, presentationId: String)
object SetPresentationDownloadableEvtMsg { val NAME = "SetPresentationDownloadableEvtMsg" }
case class SetPresentationDownloadableEvtMsg(header: BbbClientMsgHeader, body: SetPresentationDownloadableEvtMsgBody) extends BbbCoreMsg
case class SetPresentationDownloadableEvtMsgBody(podId: String, presentationId: String, downloadable: Boolean, presFilename: String)
case class SetPresentationDownloadableEvtMsgBody(podId: String, presentationId: String, downloadable: Boolean,
presFilename: String, downloadableExtension: String)
object ResizeAndMovePageEvtMsg { val NAME = "ResizeAndMovePageEvtMsg" }
case class ResizeAndMovePageEvtMsg(header: BbbClientMsgHeader, body: ResizeAndMovePageEvtMsgBody) extends BbbCoreMsg

View File

@ -105,21 +105,33 @@ public final class Util {
return path;
}
public static File getPresFileDownloadMarker(File presBaseDir, String presId) {
public static File getPresFileDownloadMarker(File presBaseDir, String presId, String downloadableExtension) {
if (presBaseDir != null) {
String downloadMarker = presId.concat(".downloadable");
String downloadMarker = presId.concat(".").concat(downloadableExtension).concat(".downloadable");
return new File(presBaseDir.getAbsolutePath() + File.separatorChar + downloadMarker);
}
return null;
}
public static void deleteAllDownloadableMarksInPresentations(File presFileDir) {
// Delete files with .downloadable at the end of its filename
File[] presFiles = presFileDir.listFiles();
for (File presFile : presFiles) {
if (presFile.isFile() && presFile.getName().endsWith(".downloadable")) {
presFile.delete();
}
}
}
public static void makePresentationDownloadable(
File presFileDir,
String presId,
boolean downloadable
boolean downloadable,
String downloadableExtension
) throws IOException {
File downloadMarker = Util.getPresFileDownloadMarker(presFileDir, presId);
File downloadMarker = Util.getPresFileDownloadMarker(presFileDir, presId, downloadableExtension);
if (downloadable && downloadMarker != null && ! downloadMarker.exists()) {
Util.deleteAllDownloadableMarksInPresentations(presFileDir);
downloadMarker.createNewFile();
} else if (!downloadable && downloadMarker != null && downloadMarker.exists()) {
downloadMarker.delete();

View File

@ -5,11 +5,14 @@ public class MakePresentationDownloadableMsg implements IMessage {
public final String presId;
public final String presFilename;
public final Boolean downloadable;
public final String downloadableExtension;
public MakePresentationDownloadableMsg(String meetingId, String presId, String presFilename, Boolean downloadable) {
public MakePresentationDownloadableMsg(String meetingId, String presId, String presFilename,
Boolean downloadable, String downloadableExtension) {
this.meetingId = meetingId;
this.presId = presId;
this.presFilename = presFilename;
this.downloadable = downloadable;
this.downloadableExtension = downloadableExtension;
}
}

View File

@ -78,7 +78,7 @@ public class RecordingServiceFileImpl implements RecordingService {
public void processMakePresentationDownloadableMsg(MakePresentationDownloadableMsg msg) {
try {
File presDir = Util.getPresentationDir(presentationBaseDir, msg.meetingId, msg.presId);
Util.makePresentationDownloadable(presDir, msg.presId, msg.downloadable);
Util.makePresentationDownloadable(presDir, msg.presId, msg.downloadable, msg.downloadableExtension);
} catch (IOException e) {
log.error("Failed to make presentation downloadable: {}", e);
}
@ -96,7 +96,7 @@ public class RecordingServiceFileImpl implements RecordingService {
String presFilenameExt = FilenameUtils.getExtension(presFilename);
File presDir = Util.getPresentationDir(presentationBaseDir, meetingId, presId);
File downloadMarker = Util.getPresFileDownloadMarker(presDir, presId);
File downloadMarker = Util.getPresFileDownloadMarker(presDir, presId, presFilenameExt);
if (presDir != null && downloadMarker != null && downloadMarker.exists()) {
String safePresFilename = presId.concat(".").concat(presFilenameExt);
File presFile = new File(presDir.getAbsolutePath() + File.separatorChar + safePresFilename);

View File

@ -227,14 +227,4 @@ public final class UploadedPresentation {
String nameWithoutExtension = FilenameUtils.removeExtension(name);
this.filenameConverted = nameWithoutExtension.concat("." + newExtension);
}
public void deleteOriginalFile() {
String pathToFileWithoutExtension = FilenameUtils.removeExtension(uploadedFile.getPath());
String newExtension = FilenameUtils.getExtension(uploadedFile.getPath());
String originalExtension = FilenameUtils.getExtension(name);
if (!originalExtension.equals("pdf") && newExtension.equals("pdf")) {
File originalFile = new File(pathToFileWithoutExtension + "." + originalExtension);
originalFile.delete();
}
}
}

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.presentation.imp;
import com.google.gson.Gson;
import org.apache.commons.io.FilenameUtils;
import org.bigbluebutton.api.Util;
import org.bigbluebutton.presentation.*;
import org.bigbluebutton.presentation.messages.*;
@ -65,7 +66,15 @@ public class PresentationFileProcessor {
private void processMakePresentationDownloadableMsg(UploadedPresentation pres) {
try {
File presentationFileDir = pres.getUploadedFile().getParentFile();
Util.makePresentationDownloadable(presentationFileDir, pres.getId(), pres.isDownloadable());
if (!pres.getFilenameConverted().equals("")) {
String fileExtensionConverted = FilenameUtils.getExtension(pres.getFilenameConverted());
Util.makePresentationDownloadable(presentationFileDir, pres.getId(), pres.isDownloadable(),
fileExtensionConverted);
}
String fileExtensionOriginal = FilenameUtils.getExtension(pres.getName());
Util.makePresentationDownloadable(presentationFileDir, pres.getId(), pres.isDownloadable(),
fileExtensionOriginal);
} catch (IOException e) {
log.error("Failed to make presentation downloadable: {}", e);
}

View File

@ -108,9 +108,6 @@ public class SlidesGenerationProgressNotifier {
log.error("GeneratedSlidesInfoHelper was not set. Could not notify interested listeners.");
return;
}
// Completed conversion -> delete original file
pres.deleteOriginalFile();
DocPageCompletedProgress progress = new DocPageCompletedProgress(pres.getPodId(), pres.getMeetingId(),
pres.getId(), pres.getTemporaryPresentationId(), pres.getId(),
pres.getName(), "notUsedYet", "notUsedYet",

View File

@ -184,7 +184,8 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
val presId = msg.body.presentationId
val downloadable = msg.body.downloadable
val presFilename = msg.body.presFilename
val m = new MakePresentationDownloadableMsg(meetingId, presId, presFilename, downloadable)
val downloadableExtension = msg.body.downloadableExtension;
val m = new MakePresentationDownloadableMsg(meetingId, presId, presFilename, downloadable, downloadableExtension)
olgMsgGW.handle(m)
}

View File

@ -70,9 +70,11 @@ class NewPresFileAvailableMsg {
userId: '',
},
body: {
fileURI: link,
annotatedFileURI: link,
originalFileURI: '',
convertedFileURI: '',
presId: exportJob.presId,
typeOfExport: "Annotated",
fileStateType: 'Annotated',
},
},
};

View File

@ -4,13 +4,17 @@ import setPresentationDownloadable from '../modifiers/setPresentationDownloadabl
export default async function handlePresentationDownloadableSet({ body }, meetingId) {
check(body, Object);
const { presentationId, podId, downloadable } = body;
const {
presentationId, podId, downloadable, downloadableExtension,
} = body;
check(meetingId, String);
check(presentationId, String);
check(podId, String);
check(downloadable, Boolean);
check(downloadableExtension, String);
const result = await setPresentationDownloadable(meetingId, podId, presentationId, downloadable);
const result = await setPresentationDownloadable(meetingId, podId, presentationId, downloadable,
downloadableExtension);
return result;
}

View File

@ -7,16 +7,36 @@ export default async function handlePresentationExport({ body }, meetingId) {
check(body, Object);
check(meetingId, String);
const { fileURI, presId, typeOfExport } = body;
const {
annotatedFileURI,
originalFileURI,
convertedFileURI,
presId,
fileStateType,
} = body;
check(fileURI, String);
check(annotatedFileURI, String);
check(originalFileURI, String);
check(convertedFileURI, String);
check(presId, String);
check(typeOfExport, String);
check(fileStateType, String);
if (typeOfExport === 'Original') {
await setOriginalUriDownload(meetingId, presId, fileURI);
if (fileStateType === 'Original' || fileStateType === 'Converted') {
if (fileStateType === 'Converted') {
await setOriginalUriDownload(
meetingId,
presId,
convertedFileURI,
);
} else {
await sendExportedPresentationChatMsg(meetingId, presId, fileURI, typeOfExport);
await setOriginalUriDownload(
meetingId,
presId,
originalFileURI,
);
}
} else {
await sendExportedPresentationChatMsg(meetingId, presId, annotatedFileURI, fileStateType);
}
await setPresentationExporting(meetingId, presId, { status: 'EXPORTED' });
}

View File

@ -4,7 +4,7 @@ import Presentations from '/imports/api/presentations';
const DEFAULT_FILENAME = 'annotated_slides.pdf';
export default async function sendExportedPresentationChatMsg(meetingId,
presentationId, fileURI, typeOfExport) {
presentationId, fileURI, fileStateType) {
const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const PUBLIC_CHAT_SYSTEM_ID = CHAT_CONFIG.system_userid;
@ -18,7 +18,7 @@ export default async function sendExportedPresentationChatMsg(meetingId,
type: 'presentation',
fileURI,
filename: pres?.name || DEFAULT_FILENAME,
typeOfExport,
fileStateType,
};
const payload = {

View File

@ -7,7 +7,7 @@ import Presentations from '/imports/api/presentations';
const EXPORTING_THRESHOLD_PER_SLIDE = 2500;
export default async function exportPresentation(presentationId, type) {
export default async function exportPresentation(presentationId, fileStateType) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'MakePresentationDownloadReqMsg';
@ -22,7 +22,7 @@ export default async function exportPresentation(presentationId, type) {
const payload = {
presId: presentationId,
allPages: true,
typeOfExport: type,
fileStateType,
pages: [],
};

View File

@ -3,7 +3,7 @@ import { check } from 'meteor/check';
import { extractCredentials } from '/imports/api/common/server/helpers';
import Logger from '/imports/startup/server/logger';
export default function setPresentationDownloadable(presentationId, downloadable) {
export default function setPresentationDownloadable(presentationId, downloadable, fileStateType) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'SetPresentationDownloadablePubMsg';
@ -15,11 +15,13 @@ export default function setPresentationDownloadable(presentationId, downloadable
check(requesterUserId, String);
check(downloadable, Match.Maybe(Boolean));
check(presentationId, String);
check(fileStateType, Match.Maybe(String));
const payload = {
presentationId,
podId: 'DEFAULT_PRESENTATION_POD',
downloadable,
fileStateType,
};
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);

View File

@ -3,11 +3,12 @@ import Presentations from '/imports/api/presentations';
import Logger from '/imports/startup/server/logger';
export default async function setPresentationDownloadable(meetingId, podId,
presentationId, downloadable) {
presentationId, downloadable, extensionToBeDownloadable) {
check(meetingId, String);
check(presentationId, String);
check(podId, String);
check(downloadable, Boolean);
check(extensionToBeDownloadable, String);
const selector = {
meetingId,
@ -15,9 +16,14 @@ export default async function setPresentationDownloadable(meetingId, podId,
id: presentationId,
};
let downloadableExtension = extensionToBeDownloadable;
if (!downloadable) {
downloadableExtension = '';
}
const modifier = {
$set: {
downloadable,
downloadableExtension,
},
};

View File

@ -344,14 +344,14 @@ const removePackagedClassAttribute = (classnames, attribute) => {
});
};
const getExportedPresentationString = (fileURI, filename, intl, typeOfExport) => {
const intlTypeOfExport = typeOfExport === 'Original' ? intlMessages.original : intlMessages.currentState;
const getExportedPresentationString = (fileURI, filename, intl, fileStateType) => {
const intlFileStateType = fileStateType === 'Original' ? intlMessages.original : intlMessages.currentState;
const href = `${APP.bbbWebBase}/${fileURI}`;
const warningIcon = '<i class="icon-bbb-warning"></i>';
const label = `<span>${intl.formatMessage(intlMessages.download)}</span>`;
const notAccessibleWarning = `<span title="${intl.formatMessage(intlMessages.notAccessibleWarning)}">${warningIcon}</span>`;
const link = `<a aria-label="${intl.formatMessage(intlMessages.notAccessibleWarning)}" href=${href} type="application/pdf" target="_blank" rel="noopener, noreferrer" download>${label}&nbsp;${notAccessibleWarning}</a>`;
const name = `<span>${filename} (${intl.formatMessage(intlTypeOfExport)})</span>`;
const name = `<span>${filename} (${intl.formatMessage(intlFileStateType)})</span>`;
return `${name}</br>${link}`;
};

View File

@ -480,7 +480,7 @@ class TimeWindowChatItem extends PureComponent {
type="presentation"
key={messages[0].id}
text={getExportedPresentationString(extra.fileURI,
extra.filename, intl, extra.typeOfExport)}
extra.filename, intl, extra.fileStateType)}
time={messages[0].time}
chatAreaId={chatAreaId}
lastReadMessageTime={lastReadMessageTime}

View File

@ -29,7 +29,7 @@ const propTypes = {
fileSizeMax: PropTypes.number.isRequired,
filePagesMax: PropTypes.number.isRequired,
handleSave: PropTypes.func.isRequired,
dispatchTogglePresentationDownloadable: PropTypes.func.isRequired,
dispatchChangePresentationDownloadable: PropTypes.func.isRequired,
fileValidMimeTypes: PropTypes.arrayOf(PropTypes.shape).isRequired,
presentations: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.string.isRequired,
@ -362,7 +362,7 @@ class PresentationUploader extends Component {
this.deepMergeUpdateFileKey = this.deepMergeUpdateFileKey.bind(this);
this.updateFileKey = this.updateFileKey.bind(this);
this.getPresentationsToShow = this.getPresentationsToShow.bind(this);
this.handleToggleDownloadable = this.handleToggleDownloadable.bind(this);
this.handleDownloadableChange = this.handleDownloadableChange.bind(this);
}
componentDidUpdate(prevProps) {
@ -418,6 +418,16 @@ class PresentationUploader extends Component {
shouldUpdateState = true;
}
if (currentPropPres?.downloadableExtension !== prevPropPres?.downloadableExtension) {
presentation.downloadableExtension = currentPropPres.downloadableExtension;
shouldUpdateState = true;
}
if (currentPropPres?.filenameConverted !== prevPropPres?.filenameConverted) {
presentation.filenameConverted = currentPropPres.filenameConverted;
shouldUpdateState = true;
}
modPresentation.conversion = currentPropPres.conversion;
modPresentation.isRemovable = currentPropPres.isRemovable;
@ -599,7 +609,7 @@ class PresentationUploader extends Component {
handleSave,
selectedToBeNextCurrent,
presentations: propPresentations,
dispatchTogglePresentationDownloadable,
dispatchChangePresentationDownloadable,
} = this.props;
const { disableActions, presentations } = this.state;
const presentationsToSave = presentations;
@ -620,7 +630,7 @@ class PresentationUploader extends Component {
(p) => p.id === item.id && p.isDownloadable !== item.isDownloadable,
);
if (didDownloadableStateChange) {
dispatchTogglePresentationDownloadable(item, item.isDownloadable);
dispatchChangePresentationDownloadable(item, item.isDownloadable);
}
}
});
@ -660,12 +670,10 @@ class PresentationUploader extends Component {
return null;
}
handleToggleDownloadable(item) {
const { dispatchTogglePresentationDownloadable } = this.props;
handleDownloadableChange(item, fileStateType, downloadable) {
const { dispatchChangePresentationDownloadable } = this.props;
const oldDownloadableState = item.isDownloadable;
dispatchTogglePresentationDownloadable(item, !oldDownloadableState);
dispatchChangePresentationDownloadable(item, downloadable, fileStateType);
}
handleDismiss() {
@ -692,7 +700,7 @@ class PresentationUploader extends Component {
);
}
handleDownloadingOfPresentation(item, type) {
handleDownloadingOfPresentation(item, fileStateType) {
const {
exportPresentation,
intl,
@ -702,7 +710,7 @@ class PresentationUploader extends Component {
this.deepMergeUpdateFileKey(item.id, 'exportation', exportation);
if (exportation.status === EXPORT_STATUSES.EXPORTED && stopped) {
if (type === 'Original') {
if (fileStateType === 'Original' || fileStateType === 'Converted') {
if (!item.isDownloadable) {
notify(intl.formatMessage(intlMessages.downloadButtonAvailable, { 0: item.filename }), 'success');
}
@ -715,7 +723,7 @@ class PresentationUploader extends Component {
EXPORT_STATUSES.RUNNING,
EXPORT_STATUSES.COLLECTING,
EXPORT_STATUSES.PROCESSING,
].includes(exportation.status) && type !== 'Original') {
].includes(exportation.status) && fileStateType === 'Annotated') {
this.setState((prevState) => {
prevState.presExporting.add(item.id);
return {
@ -750,7 +758,7 @@ class PresentationUploader extends Component {
}
};
exportPresentation(item.id, observer, type);
exportPresentation(item.id, observer, fileStateType);
}
getPresentationsToShow() {
@ -1079,11 +1087,11 @@ class PresentationUploader extends Component {
isDownloadable={isDownloadable}
allowDownloadOriginal={allowDownloadOriginal}
allowDownloadWithAnnotations={allowDownloadWithAnnotations}
handleToggleDownloadable={this.handleToggleDownloadable}
handleDownloadableChange={this.handleDownloadableChange}
item={item}
closeModal={() => Session.set('showUploadPresentationView', false)}
handleDownloadingOfPresentation={(type) => this
.handleDownloadingOfPresentation(item, type)}
handleDownloadingOfPresentation={(fileStateType) => this
.handleDownloadingOfPresentation(item, fileStateType)}
/>
) : null}
{isRemovable ? (

View File

@ -36,7 +36,7 @@ export default withTracker(() => {
const {
dispatchDisableDownloadable,
dispatchEnableDownloadable,
dispatchTogglePresentationDownloadable,
dispatchChangePresentationDownloadable,
exportPresentation,
} = Service;
const isOpen = isPresentationEnabled() && (Session.get('showUploadPresentationView') || false);
@ -56,7 +56,7 @@ export default withTracker(() => {
renderPresentationItemStatus: PresUploaderToast.renderPresentationItemStatus,
dispatchDisableDownloadable,
dispatchEnableDownloadable,
dispatchTogglePresentationDownloadable,
dispatchChangePresentationDownloadable,
exportPresentation,
isOpen,
selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null,

View File

@ -38,22 +38,36 @@ const propTypes = {
formatMessage: PropTypes.func.isRequired,
}).isRequired,
handleDownloadingOfPresentation: PropTypes.func.isRequired,
handleToggleDownloadable: PropTypes.func.isRequired,
handleDownloadableChange: PropTypes.func.isRequired,
isDownloadable: PropTypes.bool.isRequired,
allowDownloadOriginal: PropTypes.bool.isRequired,
allowDownloadWithAnnotations: PropTypes.bool.isRequired,
item: PropTypes.shape({
id: PropTypes.string.isRequired,
filename: PropTypes.string.isRequired,
filenameConverted: PropTypes.string,
isCurrent: PropTypes.bool.isRequired,
temporaryPresentationId: PropTypes.string.isRequired,
temporaryPresentationId: PropTypes.string,
isDownloadable: PropTypes.bool.isRequired,
isRemovable: PropTypes.bool.isRequired,
conversion: PropTypes.shape,
upload: PropTypes.shape,
exportation: PropTypes.shape,
uploadTimestamp: PropTypes.number.isRequired,
conversion: PropTypes.shape({
done: PropTypes.bool,
error: PropTypes.bool,
status: PropTypes.string,
numPages: PropTypes.number,
pagesCompleted: PropTypes.number,
}),
upload: PropTypes.shape({
done: PropTypes.bool,
error: PropTypes.bool,
}).isRequired,
exportation: PropTypes.shape({
status: PropTypes.string,
}),
uploadTimestamp: PropTypes.string,
downloadableExtension: PropTypes.string,
}).isRequired,
closeModal: PropTypes.func.isRequired,
isRTL: PropTypes.bool.isRequired,
disabled: PropTypes.bool.isRequired,
};
@ -72,7 +86,7 @@ class PresentationDownloadDropdown extends PureComponent {
const {
intl,
handleDownloadingOfPresentation,
handleToggleDownloadable,
handleDownloadableChange,
isDownloadable,
allowDownloadOriginal,
allowDownloadWithAnnotations,
@ -82,30 +96,57 @@ class PresentationDownloadDropdown extends PureComponent {
this.menuItems = [];
const toggleDownloadOriginalPresentation = (enableDownload) => {
handleToggleDownloadable(item);
const { filenameConverted, filename, downloadableExtension } = item;
const convertedFileExtension = filenameConverted?.split('.').slice(-1)[0];
const originalFileExtension = filename?.split('.').slice(-1)[0];
const changeDownloadOriginalOrConvertedPresentation = (enableDownload, fileStateType) => {
handleDownloadableChange(item, fileStateType, enableDownload);
if (enableDownload) {
handleDownloadingOfPresentation('Original');
handleDownloadingOfPresentation(fileStateType);
}
closeModal();
};
if (allowDownloadOriginal) {
if (!isDownloadable) {
if (isDownloadable && !!downloadableExtension
&& downloadableExtension === originalFileExtension) {
this.menuItems.push({
key: this.actionsKey[0],
dataTest: 'enableOriginalPresentationDownload',
label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload),
onClick: () => toggleDownloadOriginalPresentation(true),
dataTest: 'disableOriginalPresentationDownload',
label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload,
{ 0: originalFileExtension }),
onClick: () => changeDownloadOriginalOrConvertedPresentation(false, 'Original'),
});
} else {
this.menuItems.push({
key: this.actionsKey[0],
dataTest: 'disableOriginalPresentationDownload',
label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload),
onClick: () => toggleDownloadOriginalPresentation(false),
dataTest: 'enableOriginalPresentationDownload',
label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload,
{ 0: originalFileExtension }),
onClick: () => changeDownloadOriginalOrConvertedPresentation(true, 'Original'),
});
}
if ((!!filenameConverted && filenameConverted !== '')
&& convertedFileExtension !== originalFileExtension) {
if (isDownloadable && !!downloadableExtension
&& downloadableExtension === convertedFileExtension) {
this.menuItems.push({
key: this.actionsKey[0],
dataTest: 'disableOriginalPresentationDownload',
label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload,
{ 0: convertedFileExtension }),
onClick: () => changeDownloadOriginalOrConvertedPresentation(false, 'Converted'),
});
} else {
this.menuItems.push({
key: this.actionsKey[0],
dataTest: 'enableOriginalPresentationDownload',
label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload,
{ 0: convertedFileExtension }),
onClick: () => changeDownloadOriginalOrConvertedPresentation(true, 'Converted'),
});
}
}
}
if (allowDownloadWithAnnotations) {
this.menuItems.push({
@ -123,7 +164,7 @@ class PresentationDownloadDropdown extends PureComponent {
}
render() {
const { intl, isRTL, disabled } = this.props;
const { intl, disabled } = this.props;
const customStyles = { zIndex: 9999 };
@ -147,8 +188,8 @@ class PresentationDownloadDropdown extends PureComponent {
elevation: 2,
getcontentanchorel: null,
fullwidth: 'true',
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
anchorOrigin: { vertical: 'bottom', horizontal: 'left' },
transformOrigin: { vertical: 'top', horizontal: 'left' },
}}
actions={this.getAvailableActions()}
/>

View File

@ -3,36 +3,11 @@ import PropTypes from 'prop-types';
import Styled from './styles';
const propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
children: PropTypes.shape.isRequired,
children: PropTypes.shape({}).isRequired,
disabled: PropTypes.bool.isRequired,
onClick: PropTypes.func.isRequired,
onDoubleClick: PropTypes.func.isRequired,
onMouseDown: PropTypes.func.isRequired,
onMouseUp: PropTypes.func.isRequired,
onKeyPress: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired,
onKeyUp: PropTypes.func.isRequired,
};
class PresentationDownloadDropdownWrapper extends PureComponent {
constructor(props) {
super(props);
// Bind Mouse Event Handlers
this.internalClickHandler = this.internalClickHandler.bind(this);
this.internalDoubleClickHandler = this.internalDoubleClickHandler.bind(this);
this.internalMouseDownHandler = this.internalMouseDownHandler.bind(this);
this.internalMouseUpHandler = this.internalMouseUpHandler.bind(this);
// Bind Keyboard Event Handlers
this.internalKeyPressHandler = this.internalKeyPressHandler.bind(this);
this.internalKeyDownHandler = this.internalKeyDownHandler.bind(this);
this.internalKeyUpHandler = this.internalKeyUpHandler.bind(this);
}
validateDisabled(eventHandler, ...args) {
const { disabled } = this.props;
if (!disabled && typeof eventHandler === 'function') {
@ -42,43 +17,6 @@ class PresentationDownloadDropdownWrapper extends PureComponent {
return null;
}
// Define Mouse Event Handlers
internalClickHandler(...args) {
const { onClick } = this.props;
return this.validateDisabled(onClick, ...args);
}
internalDoubleClickHandler(...args) {
const { onDoubleClick } = this.props;
return this.validateDisabled(onDoubleClick, ...args);
}
internalMouseDownHandler(...args) {
const { onMouseDown } = this.props;
return this.validateDisabled(onMouseDown, ...args);
}
internalMouseUpHandler(...args) {
const { onMouseUp } = this.props;
return this.validateDisabled(onMouseUp, ...args);
}
// Define Keyboard Event Handlers
internalKeyPressHandler(...args) {
const { onKeyPress } = this.props;
return this.validateDisabled(onKeyPress, ...args);
}
internalKeyDownHandler(...args) {
const { onKeyDown } = this.props;
return this.validateDisabled(onKeyDown, ...args);
}
internalKeyUpHandler(...args) {
const { onKeyUp } = this.props;
return this.validateDisabled(onKeyUp, ...args);
}
render() {
const {
disabled,
@ -89,15 +27,6 @@ class PresentationDownloadDropdownWrapper extends PureComponent {
<Styled.DropdownMenuWrapper
disabled={disabled}
aria-disabled={disabled}
// Render Mouse event handlers
onClick={this.internalClickHandler}
onDoubleClick={this.internalDoubleClickHandler}
onMouseDown={this.internalMouseDownHandler}
onMouseUp={this.internalMouseUpHandler}
// Render Keyboard event handlers
onKeyPress={this.internalKeyPressHandler}
onKeyDown={this.internalKeyDownHandler}
onKeyUp={this.internalKeyUpHandler}
>
{children}
</Styled.DropdownMenuWrapper>

View File

@ -56,6 +56,8 @@ const getPresentations = () => Presentations
id,
name,
exportation,
filenameConverted,
downloadableExtension,
} = presentation;
const uploadTimestamp = id.split('-').pop();
@ -72,11 +74,13 @@ const getPresentations = () => Presentations
conversion: conversion || { done: true, error: false },
uploadTimestamp,
exportation: exportation || { error: false },
filenameConverted,
downloadableExtension,
};
});
const dispatchTogglePresentationDownloadable = (presentation, newState) => {
makeCall('setPresentationDownloadable', presentation.id, newState);
const dispatchChangePresentationDownloadable = (presentation, newState, fileStateType) => {
makeCall('setPresentationDownloadable', presentation.id, newState, fileStateType);
};
const observePresentationConversion = (
@ -378,7 +382,7 @@ const getExternalUploadData = () => {
};
};
const exportPresentation = (presentationId, observer, type) => {
const exportPresentation = (presentationId, observer, fileStateType) => {
let lastStatus = {};
Tracker.autorun((c) => {
@ -407,7 +411,7 @@ const exportPresentation = (presentationId, observer, type) => {
});
});
makeCall('exportPresentation', presentationId, type);
makeCall('exportPresentation', presentationId, fileStateType);
};
function handleFiledrop(files, files2, that, intl, intlMessages) {
@ -483,7 +487,7 @@ export default {
handleSavePresentation,
getPresentations,
persistPresentationChanges,
dispatchTogglePresentationDownloadable,
dispatchChangePresentationDownloadable,
setPresentation,
requestPresentationUploadToken,
getExternalUploadData,

View File

@ -287,9 +287,9 @@
"app.presentationUploader.sent": "Sent",
"app.presentationUploader.exportingTimeout": "The export is taking too long...",
"app.presentationUploader.export": "Send to chat",
"app.presentationUploader.exportCurrentStatePresentation": "Send out a download link for the presentation in its current state",
"app.presentationUploader.enableOriginalPresentationDownload": "Enable download of the original presentation",
"app.presentationUploader.disableOriginalPresentationDownload": "Disable download of the original presentation",
"app.presentationUploader.exportCurrentStatePresentation": "Send out a download link for the presentation including whiteboard annotations",
"app.presentationUploader.enableOriginalPresentationDownload": "Enable download of the presentation ({0})",
"app.presentationUploader.disableOriginalPresentationDownload": "Disable download of the original presentation ({0})",
"app.presentationUploader.dropdownExportOptions": "Export options",
"app.presentationUploader.export.linkAvailable": "Link for downloading {0} available on the public chat.",
"app.presentationUploader.export.downloadButtonAvailable": "Download button for presentation {0} is available.",