Merge pull request #17648 from GuiLeme/issue-17531
feat: Restore old downloading of original presentation
This commit is contained in:
commit
1d3bdb5802
@ -8,10 +8,11 @@ import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.util.RandomStringGenerator
|
||||
import org.bigbluebutton.core.models.{ PresentationPod, PresentationPage, PresentationInPod }
|
||||
import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage, PresentationPod }
|
||||
|
||||
import java.io.File
|
||||
|
||||
trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
this: PresentationPodHdlrs =>
|
||||
|
||||
object JobTypes {
|
||||
@ -40,20 +41,20 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildNewPresAnnFileAvailable(fileURI: String, presId: String): NewPresAnnFileAvailableMsg = {
|
||||
val header = BbbClientMsgHeader(NewPresAnnFileAvailableMsg.NAME, "not-used", "not-used")
|
||||
val body = NewPresAnnFileAvailableMsgBody(fileURI, presId)
|
||||
def buildNewPresFileAvailable(fileURI: String, presId: String, typeOfExport: String): NewPresFileAvailableMsg = {
|
||||
val header = BbbClientMsgHeader(NewPresFileAvailableMsg.NAME, "not-used", "not-used")
|
||||
val body = NewPresFileAvailableMsgBody(fileURI, presId, typeOfExport)
|
||||
|
||||
NewPresAnnFileAvailableMsg(header, body)
|
||||
NewPresFileAvailableMsg(header, body)
|
||||
}
|
||||
|
||||
def buildBroadcastNewPresAnnFileAvailable(newPresAnnFileAvailableMsg: NewPresAnnFileAvailableMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
|
||||
def buildBroadcastNewPresFileAvailable(newPresFileAvailableMsg: NewPresFileAvailableMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
val envelope = BbbCoreEnvelope(PresentationPageConvertedEventMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(NewPresAnnFileAvailableEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
val body = NewPresAnnFileAvailableEvtMsgBody(fileURI = newPresAnnFileAvailableMsg.body.fileURI, presId = newPresAnnFileAvailableMsg.body.presId)
|
||||
val event = NewPresAnnFileAvailableEvtMsg(header, body)
|
||||
|
||||
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 event = NewPresFileAvailableEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
@ -112,7 +113,7 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
}
|
||||
}
|
||||
|
||||
def handle(m: MakePresentationWithAnnotationDownloadReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def handle(m: MakePresentationDownloadReqMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val userId = m.header.userId
|
||||
@ -146,8 +147,9 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
|
||||
|
||||
val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
|
||||
val isOriginalPresentationType = m.body.typeOfExport == "Original"
|
||||
|
||||
if (annotationCount > 0) {
|
||||
if (!isOriginalPresentationType && annotationCount > 0) {
|
||||
// Send Export Job to Redis
|
||||
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
|
||||
bus.outGW.send(job)
|
||||
@ -155,15 +157,18 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
// Send Annotations to Redis
|
||||
val annotations = StoredAnnotations(jobId, presId, storeAnnotationPages)
|
||||
bus.outGW.send(buildStoreAnnotationsInRedisSysMsg(annotations, liveMeeting))
|
||||
} else {
|
||||
} else if (!isOriginalPresentationType && annotationCount == 0) {
|
||||
log.error("There are no annotations for presentation with Id {}... Ignoring", presId)
|
||||
} else if (isOriginalPresentationType) {
|
||||
// Return existing uploaded file directly
|
||||
val filename = currentPres.get.name
|
||||
val convertedFileName = currentPres.get.filenameConverted
|
||||
val filename = if (convertedFileName == "") currentPres.get.name else convertedFileName
|
||||
val presFilenameExt = filename.split("\\.").last
|
||||
|
||||
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, "DEFAULT_PRESENTATION_POD", "not-used", presId, true, filename)
|
||||
|
||||
val fileURI = List("bigbluebutton", "presentation", "download", meetingId, s"${presId}?presFilename=${presId}.${presFilenameExt}").mkString(File.separator, File.separator, "")
|
||||
val event = buildNewPresAnnFileAvailable(fileURI, presId)
|
||||
val fileURI = List("bigbluebutton", "presentation", "download", meetingId, s"${presId}?presFilename=${presId}.${presFilenameExt}&filename=${filename}").mkString(File.separator, File.separator, "")
|
||||
val event = buildNewPresFileAvailable(fileURI, presId, m.body.typeOfExport)
|
||||
|
||||
handle(event, liveMeeting, bus)
|
||||
}
|
||||
@ -221,9 +226,9 @@ trait PresentationWithAnnotationsMsgHdlr extends RightsManagementTrait {
|
||||
}
|
||||
}
|
||||
|
||||
def handle(m: NewPresAnnFileAvailableMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
log.info("Received NewPresAnnFileAvailableMsg meetingId={} presId={} fileUrl={}", liveMeeting.props.meetingProp.intId, m.body.presId, m.body.fileURI)
|
||||
bus.outGW.send(buildBroadcastNewPresAnnFileAvailable(m, liveMeeting))
|
||||
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)
|
||||
bus.outGW.send(buildBroadcastNewPresFileAvailable(m, liveMeeting))
|
||||
}
|
||||
|
||||
def handle(m: CaptureSharedNotesReqInternalMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.PresentationInPod
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
@ -22,8 +23,7 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
pres <- pod.getPresentation(msg.body.presentation.id)
|
||||
} yield {
|
||||
val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres, temporaryPresentationId,
|
||||
msg.body.presentation.isInitialPresentation)
|
||||
|
||||
msg.body.presentation.isInitialPresentation, msg.body.presentation.filenameConverted)
|
||||
PresentationSender.broadcastPresentationConversionCompletedEvtMsg(
|
||||
bus,
|
||||
meetingId,
|
||||
@ -31,7 +31,7 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
msg.header.userId,
|
||||
msg.body.messageKey,
|
||||
msg.body.code,
|
||||
presVO
|
||||
presVO,
|
||||
)
|
||||
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(
|
||||
bus,
|
||||
@ -43,8 +43,10 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
|
||||
pres.name
|
||||
)
|
||||
|
||||
val presWithConvertedName = PresentationInPod(pres.id, pres.name, pres.current, pres.pages,
|
||||
pres.downloadable, pres.removable, msg.body.presentation.filenameConverted)
|
||||
var pods = state.presentationPodManager.addPod(pod)
|
||||
pods = pods.addPresentationToPod(pod.id, pres)
|
||||
pods = pods.addPresentationToPod(pod.id, presWithConvertedName)
|
||||
|
||||
state.update(pods)
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
|
||||
with PresentationPageCountErrorPubMsgHdlr
|
||||
with PresentationUploadedFileTooLargeErrorPubMsgHdlr
|
||||
with PresentationUploadTokenReqMsgHdlr
|
||||
with PresentationWithAnnotationsMsgHdlr
|
||||
with MakePresentationDownloadReqMsgHdlr
|
||||
with ResizeAndMovePagePubMsgHdlr
|
||||
with SyncGetPresentationPodsMsgHdlr
|
||||
with RemovePresentationPodPubMsgHdlr
|
||||
|
@ -58,7 +58,7 @@ object PresentationPodsApp {
|
||||
}
|
||||
|
||||
PresentationVO(p.id, "", p.name, p.current,
|
||||
pages.toVector, p.downloadable, p.removable, false)
|
||||
pages.toVector, p.downloadable, p.removable, false, "")
|
||||
}
|
||||
|
||||
PresentationPodVO(pod.id, pod.currentPresenter, presentationVOs.toVector)
|
||||
@ -74,7 +74,7 @@ object PresentationPodsApp {
|
||||
}
|
||||
|
||||
def translatePresentationToPresentationVO(pres: PresentationInPod, temporaryPresentationId: String,
|
||||
isInitialPresentation: Boolean): PresentationVO = {
|
||||
isInitialPresentation: Boolean, filenameConverted: String): PresentationVO = {
|
||||
val pages = pres.pages.values.map { page =>
|
||||
PageVO(
|
||||
id = page.id,
|
||||
@ -89,8 +89,8 @@ object PresentationPodsApp {
|
||||
heightRatio = page.heightRatio
|
||||
)
|
||||
}
|
||||
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable, pres.removable,
|
||||
isInitialPresentation)
|
||||
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable,
|
||||
pres.removable, isInitialPresentation, filenameConverted)
|
||||
}
|
||||
|
||||
def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = {
|
||||
|
@ -11,7 +11,7 @@ object PresentationSender {
|
||||
podId: String, userId: String,
|
||||
presentationId: String,
|
||||
downloadable: Boolean,
|
||||
presFilename: String
|
||||
presFilename: String,
|
||||
): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
@ -30,7 +30,7 @@ object PresentationSender {
|
||||
bus: MessageBus,
|
||||
meetingId: String,
|
||||
podId: String, userId: String, messageKey: String,
|
||||
code: String, presentation: PresentationVO
|
||||
code: String, presentation: PresentationVO,
|
||||
): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(
|
||||
MessageTypes.BROADCAST_TO_MEETING,
|
||||
|
@ -62,6 +62,7 @@ case class PresentationInPod(
|
||||
pages: scala.collection.immutable.Map[String, PresentationPage],
|
||||
downloadable: Boolean,
|
||||
removable: Boolean,
|
||||
filenameConverted: String = "",
|
||||
)
|
||||
|
||||
object PresentationPod {
|
||||
|
@ -314,10 +314,10 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[PdfConversionInvalidErrorSysPubMsg](envelope, jsonNode)
|
||||
case AssignPresenterReqMsg.NAME =>
|
||||
routeGenericMsg[AssignPresenterReqMsg](envelope, jsonNode)
|
||||
case MakePresentationWithAnnotationDownloadReqMsg.NAME =>
|
||||
routeGenericMsg[MakePresentationWithAnnotationDownloadReqMsg](envelope, jsonNode)
|
||||
case NewPresAnnFileAvailableMsg.NAME =>
|
||||
routeGenericMsg[NewPresAnnFileAvailableMsg](envelope, jsonNode)
|
||||
case MakePresentationDownloadReqMsg.NAME =>
|
||||
routeGenericMsg[MakePresentationDownloadReqMsg](envelope, jsonNode)
|
||||
case NewPresFileAvailableMsg.NAME =>
|
||||
routeGenericMsg[NewPresFileAvailableMsg](envelope, jsonNode)
|
||||
case PresAnnStatusMsg.NAME =>
|
||||
routeGenericMsg[PresAnnStatusMsg](envelope, jsonNode)
|
||||
|
||||
|
@ -505,8 +505,8 @@ class MeetingActor(
|
||||
// Presentation
|
||||
case m: PreuploadedPresentationsSysPubMsg => presentationApp2x.handle(m, liveMeeting, msgBus)
|
||||
case m: AssignPresenterReqMsg => state = handlePresenterChange(m, state)
|
||||
case m: MakePresentationWithAnnotationDownloadReqMsg => presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: NewPresAnnFileAvailableMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
|
||||
case m: MakePresentationDownloadReqMsg => presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: NewPresFileAvailableMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
|
||||
case m: PresAnnStatusMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
|
||||
case m: PadCapturePubMsg => presentationPodsApp.handle(m, liveMeeting, msgBus)
|
||||
|
||||
|
@ -117,8 +117,8 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
|
||||
//case m: PresentationPageConvertedEventMsg => logMessage(msg)
|
||||
// case m: StoreAnnotationsInRedisSysMsg => logMessage(msg)
|
||||
// case m: StoreExportJobInRedisSysMsg => logMessage(msg)
|
||||
case m: MakePresentationWithAnnotationDownloadReqMsg => logMessage(msg)
|
||||
case m: NewPresAnnFileAvailableMsg => logMessage(msg)
|
||||
case m: MakePresentationDownloadReqMsg => logMessage(msg)
|
||||
case m: NewPresFileAvailableMsg => logMessage(msg)
|
||||
case m: PresentationPageConversionStartedSysMsg => logMessage(msg)
|
||||
case m: PresentationConversionEndedSysMsg => logMessage(msg)
|
||||
case m: PresentationConversionRequestReceivedSysMsg => logMessage(msg)
|
||||
|
@ -2,7 +2,7 @@ package org.bigbluebutton.common2.domain
|
||||
|
||||
case class PresentationVO(id: String, temporaryPresentationId: String, name: String, current: Boolean = false,
|
||||
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean,
|
||||
isInitialPresentation: Boolean)
|
||||
isInitialPresentation: Boolean, filenameConverted: String)
|
||||
|
||||
case class PageVO(id: String, num: Int, thumbUri: String = "",
|
||||
txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0,
|
||||
|
@ -10,13 +10,13 @@ object PreuploadedPresentationsSysPubMsg { val NAME = "PreuploadedPresentationsS
|
||||
case class PreuploadedPresentationsSysPubMsg(header: BbbClientMsgHeader, body: PreuploadedPresentationsSysPubMsgBody) extends StandardMsg
|
||||
case class PreuploadedPresentationsSysPubMsgBody(presentations: Vector[PresentationVO])
|
||||
|
||||
object MakePresentationWithAnnotationDownloadReqMsg { val NAME = "MakePresentationWithAnnotationDownloadReqMsg" }
|
||||
case class MakePresentationWithAnnotationDownloadReqMsg(header: BbbClientMsgHeader, body: MakePresentationWithAnnotationDownloadReqMsgBody) extends StandardMsg
|
||||
case class MakePresentationWithAnnotationDownloadReqMsgBody(presId: String, allPages: Boolean, pages: List[Int])
|
||||
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)
|
||||
|
||||
object NewPresAnnFileAvailableMsg { val NAME = "NewPresAnnFileAvailableMsg" }
|
||||
case class NewPresAnnFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresAnnFileAvailableMsgBody) extends StandardMsg
|
||||
case class NewPresAnnFileAvailableMsgBody(fileURI: String, presId: String)
|
||||
object NewPresFileAvailableMsg { val NAME = "NewPresFileAvailableMsg" }
|
||||
case class NewPresFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableMsgBody) extends StandardMsg
|
||||
case class NewPresFileAvailableMsgBody(fileURI: String, presId: String, typeOfExport: String)
|
||||
|
||||
object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" }
|
||||
case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg
|
||||
@ -37,9 +37,9 @@ object NewPresentationEvtMsg { val NAME = "NewPresentationEvtMsg" }
|
||||
case class NewPresentationEvtMsg(header: BbbClientMsgHeader, body: NewPresentationEvtMsgBody) extends BbbCoreMsg
|
||||
case class NewPresentationEvtMsgBody(presentation: PresentationVO)
|
||||
|
||||
object NewPresAnnFileAvailableEvtMsg { val NAME = "NewPresAnnFileAvailableEvtMsg" }
|
||||
case class NewPresAnnFileAvailableEvtMsg(header: BbbClientMsgHeader, body: NewPresAnnFileAvailableEvtMsgBody) extends BbbCoreMsg
|
||||
case class NewPresAnnFileAvailableEvtMsgBody(fileURI: String, presId: String)
|
||||
object NewPresFileAvailableEvtMsg { val NAME = "NewPresFileAvailableEvtMsg" }
|
||||
case class NewPresFileAvailableEvtMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableEvtMsgBody) extends BbbCoreMsg
|
||||
case class NewPresFileAvailableEvtMsgBody(fileURI: String, presId: String, typeOfExport: String)
|
||||
|
||||
object PresAnnStatusEvtMsg { val NAME = "PresAnnStatusEvtMsg" }
|
||||
case class PresAnnStatusEvtMsg(header: BbbClientMsgHeader, body: PresAnnStatusEvtMsgBody) extends BbbCoreMsg
|
||||
|
@ -121,6 +121,8 @@ public final class Util {
|
||||
File downloadMarker = Util.getPresFileDownloadMarker(presFileDir, presId);
|
||||
if (downloadable && downloadMarker != null && ! downloadMarker.exists()) {
|
||||
downloadMarker.createNewFile();
|
||||
} else if (!downloadable && downloadMarker != null && downloadMarker.exists()) {
|
||||
downloadMarker.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ package org.bigbluebutton.presentation;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
public final class UploadedPresentation {
|
||||
private final String podId;
|
||||
@ -34,6 +35,7 @@ public final class UploadedPresentation {
|
||||
private String fileType = "unknown";
|
||||
private int numberOfPages = 0;
|
||||
private String conversionStatus;
|
||||
private String filenameConverted;
|
||||
private final String baseUrl;
|
||||
private boolean isDownloadable = false;
|
||||
private boolean isRemovable = true;
|
||||
@ -212,4 +214,27 @@ public final class UploadedPresentation {
|
||||
public boolean getIsInitialPresentation() {
|
||||
return isInitialPresentation;
|
||||
}
|
||||
|
||||
public String getFilenameConverted() {
|
||||
if (filenameConverted != null) {
|
||||
return filenameConverted;
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void generateFilenameConverted(String newExtension) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,7 @@ package org.bigbluebutton.presentation.imp;
|
||||
import com.google.gson.Gson;
|
||||
import org.bigbluebutton.api.Util;
|
||||
import org.bigbluebutton.presentation.*;
|
||||
import org.bigbluebutton.presentation.messages.DocPageConversionStarted;
|
||||
import org.bigbluebutton.presentation.messages.DocPageCountExceeded;
|
||||
import org.bigbluebutton.presentation.messages.DocPageCountFailed;
|
||||
import org.bigbluebutton.presentation.messages.PresentationConvertMessage;
|
||||
import org.bigbluebutton.presentation.messages.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -76,6 +73,7 @@ public class PresentationFileProcessor {
|
||||
|
||||
private void processUploadedPresentation(UploadedPresentation pres) {
|
||||
if (SupportedFileTypes.isPdfFile(pres.getFileType())) {
|
||||
pres.generateFilenameConverted("pdf");
|
||||
determineNumberOfPages(pres);
|
||||
sendDocPageConversionStartedProgress(pres);
|
||||
PresentationConvertMessage msg = new PresentationConvertMessage(pres);
|
||||
|
@ -108,12 +108,14 @@ 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",
|
||||
pres.isDownloadable(), pres.isRemovable(), ConversionMessageConstants.CONVERSION_COMPLETED_KEY,
|
||||
pres.getNumberOfPages(), generateBasePresUrl(pres), pres.isCurrent(), pres.getIsInitialPresentation());
|
||||
pres.getNumberOfPages(), generateBasePresUrl(pres), pres.isCurrent(), pres.getIsInitialPresentation(), pres.getFilenameConverted());
|
||||
messagingService.sendDocConversionMsg(progress);
|
||||
}
|
||||
|
||||
|
@ -16,11 +16,14 @@ public class DocPageCompletedProgress implements IDocConversionMsg {
|
||||
public final String presBaseUrl;
|
||||
public final Boolean current;
|
||||
public final Boolean isInitialPresentation;
|
||||
public final String filenameConverted;
|
||||
|
||||
|
||||
public DocPageCompletedProgress(String podId, String meetingId, String presId, String temporaryPresentationId, String presInstance,
|
||||
String filename, String uploaderId, String authzToken,
|
||||
Boolean downloadable, Boolean removable, String key,
|
||||
Integer numPages, String presBaseUrl, Boolean current, Boolean isInitialPresentation) {
|
||||
Integer numPages, String presBaseUrl, Boolean current,
|
||||
Boolean isInitialPresentation, String filenameConverted) {
|
||||
this.podId = podId;
|
||||
this.meetingId = meetingId;
|
||||
this.presId = presId;
|
||||
@ -36,5 +39,6 @@ public class DocPageCompletedProgress implements IDocConversionMsg {
|
||||
this.presBaseUrl = presBaseUrl;
|
||||
this.current = current;
|
||||
this.isInitialPresentation = isInitialPresentation;
|
||||
this.filenameConverted = filenameConverted;
|
||||
}
|
||||
}
|
||||
|
@ -148,8 +148,9 @@ object MsgBuilder {
|
||||
|
||||
val pages = generatePresentationPages(msg.presId, msg.numPages.intValue(), msg.presBaseUrl)
|
||||
val presentation = PresentationVO(msg.presId, msg.temporaryPresentationId, msg.filename,
|
||||
current = msg.current.booleanValue(), pages.values.toVector, msg.downloadable.booleanValue(), msg.removable.booleanValue(),
|
||||
isInitialPresentation = msg.isInitialPresentation)
|
||||
current = msg.current.booleanValue(), pages.values.toVector, msg.downloadable.booleanValue(),
|
||||
msg.removable.booleanValue(),
|
||||
isInitialPresentation = msg.isInitialPresentation, msg.filenameConverted)
|
||||
|
||||
val body = PresentationConversionCompletedSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
|
||||
code = msg.key, presentation)
|
||||
|
@ -25,7 +25,7 @@
|
||||
"notifier": {
|
||||
"pod_id": "DEFAULT_PRESENTATION_POD",
|
||||
"is_downloadable": "false",
|
||||
"msgName": "NewPresAnnFileAvailableMsg"
|
||||
"msgName": "NewPresFileAvailableMsg"
|
||||
},
|
||||
"bbbWebAPI": "http://127.0.0.1:8090",
|
||||
"bbbWebPublicAPI": "/bigbluebutton/",
|
||||
|
@ -53,7 +53,7 @@ class PresAnnStatusMsg {
|
||||
}
|
||||
};
|
||||
|
||||
class NewPresAnnFileAvailableMsg {
|
||||
class NewPresFileAvailableMsg {
|
||||
constructor(exportJob, link) {
|
||||
this.message = {
|
||||
envelope: {
|
||||
@ -72,6 +72,7 @@ class NewPresAnnFileAvailableMsg {
|
||||
body: {
|
||||
fileURI: link,
|
||||
presId: exportJob.presId,
|
||||
typeOfExport: "Annotated",
|
||||
},
|
||||
},
|
||||
};
|
||||
@ -84,5 +85,5 @@ class NewPresAnnFileAvailableMsg {
|
||||
|
||||
module.exports = {
|
||||
PresAnnStatusMsg,
|
||||
NewPresAnnFileAvailableMsg,
|
||||
NewPresFileAvailableMsg,
|
||||
};
|
||||
|
@ -5,7 +5,7 @@ const FormData = require('form-data');
|
||||
const redis = require('redis');
|
||||
const axios = require('axios').default;
|
||||
const path = require('path');
|
||||
const {NewPresAnnFileAvailableMsg} = require('../lib/utils/message-builder');
|
||||
const {NewPresFileAvailableMsg} = require('../lib/utils/message-builder');
|
||||
|
||||
const {workerData} = require('worker_threads');
|
||||
const [jobType, jobId, filename] = [workerData.jobType, workerData.jobId, workerData.filename];
|
||||
@ -32,7 +32,7 @@ async function notifyMeetingActor() {
|
||||
exportJob.parentMeetingId, exportJob.parentMeetingId,
|
||||
exportJob.presId, 'pdf', jobId, filename);
|
||||
|
||||
const notification = new NewPresAnnFileAvailableMsg(exportJob, link);
|
||||
const notification = new NewPresFileAvailableMsg(exportJob, link);
|
||||
|
||||
logger.info(`Annotated PDF available at ${link}`);
|
||||
await client.publish(config.redis.channels.publish, notification.build());
|
||||
|
@ -18,5 +18,5 @@ RedisPubSub.on('PresentationConversionCompletedEvtMsg', handlePresentationAdded)
|
||||
RedisPubSub.on('RemovePresentationEvtMsg', handlePresentationRemove);
|
||||
RedisPubSub.on('SetCurrentPresentationEvtMsg', handlePresentationCurrentSet);
|
||||
RedisPubSub.on('SetPresentationDownloadableEvtMsg', handlePresentationDownloadableSet);
|
||||
RedisPubSub.on('NewPresAnnFileAvailableEvtMsg', handlePresentationExport);
|
||||
RedisPubSub.on('NewPresFileAvailableEvtMsg', handlePresentationExport);
|
||||
RedisPubSub.on('PresAnnStatusEvtMsg', handlePresentationExportToastUpdate);
|
||||
|
@ -1,16 +1,22 @@
|
||||
import { check } from 'meteor/check';
|
||||
import sendExportedPresentationChatMsg from '/imports/api/presentations/server/handlers/sendExportedPresentationChatMsg';
|
||||
import setPresentationExporting from '/imports/api/presentations/server/modifiers/setPresentationExporting';
|
||||
import setOriginalUriDownload from '/imports/api/presentations/server/modifiers/setOriginalUriDownload';
|
||||
|
||||
export default async function handlePresentationExport({ body }, meetingId) {
|
||||
check(body, Object);
|
||||
check(meetingId, String);
|
||||
|
||||
const { fileURI, presId } = body;
|
||||
const { fileURI, presId, typeOfExport } = body;
|
||||
|
||||
check(fileURI, String);
|
||||
check(presId, String);
|
||||
check(typeOfExport, String);
|
||||
|
||||
await sendExportedPresentationChatMsg(meetingId, presId, fileURI);
|
||||
if (typeOfExport === 'Original') {
|
||||
await setOriginalUriDownload(meetingId, presId, fileURI);
|
||||
} else {
|
||||
await sendExportedPresentationChatMsg(meetingId, presId, fileURI, typeOfExport);
|
||||
}
|
||||
await setPresentationExporting(meetingId, presId, { status: 'EXPORTED' });
|
||||
}
|
||||
|
@ -3,11 +3,13 @@ import Presentations from '/imports/api/presentations';
|
||||
|
||||
const DEFAULT_FILENAME = 'annotated_slides.pdf';
|
||||
|
||||
export default async function sendExportedPresentationChatMsg(meetingId, presentationId, fileURI) {
|
||||
export default async function sendExportedPresentationChatMsg(meetingId,
|
||||
presentationId, fileURI, typeOfExport) {
|
||||
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;
|
||||
const CHAT_EXPORTED_PRESENTATION_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_exported_presentation;
|
||||
const CHAT_EXPORTED_PRESENTATION_MESSAGE = CHAT_CONFIG
|
||||
.system_messages_keys.chat_exported_presentation;
|
||||
const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system;
|
||||
|
||||
const pres = await Presentations.findOneAsync({ meetingId, id: presentationId });
|
||||
@ -16,6 +18,7 @@ export default async function sendExportedPresentationChatMsg(meetingId, present
|
||||
type: 'presentation',
|
||||
fileURI,
|
||||
filename: pres?.name || DEFAULT_FILENAME,
|
||||
typeOfExport,
|
||||
};
|
||||
|
||||
const payload = {
|
||||
|
@ -3,12 +3,12 @@ import removePresentation from './methods/removePresentation';
|
||||
import setPresentationRenderedInToast from './methods/setPresentationRenderedInToast';
|
||||
import setPresentation from './methods/setPresentation';
|
||||
import setPresentationDownloadable from './methods/setPresentationDownloadable';
|
||||
import exportPresentationToChat from './methods/exportPresentationToChat';
|
||||
import exportPresentation from './methods/exportPresentation';
|
||||
|
||||
Meteor.methods({
|
||||
removePresentation,
|
||||
setPresentation,
|
||||
setPresentationDownloadable,
|
||||
exportPresentationToChat,
|
||||
exportPresentation,
|
||||
setPresentationRenderedInToast,
|
||||
});
|
||||
|
@ -7,10 +7,10 @@ import Presentations from '/imports/api/presentations';
|
||||
|
||||
const EXPORTING_THRESHOLD_PER_SLIDE = 2500;
|
||||
|
||||
export default async function exportPresentationToChat(presentationId) {
|
||||
export default async function exportPresentation(presentationId, type) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'MakePresentationWithAnnotationDownloadReqMsg';
|
||||
const EVENT_NAME = 'MakePresentationDownloadReqMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
@ -22,6 +22,7 @@ export default async function exportPresentationToChat(presentationId) {
|
||||
const payload = {
|
||||
presId: presentationId,
|
||||
allPages: true,
|
||||
typeOfExport: type,
|
||||
pages: [],
|
||||
};
|
||||
|
||||
@ -30,7 +31,8 @@ export default async function exportPresentationToChat(presentationId) {
|
||||
|
||||
const selector = { meetingId, id: presentationId };
|
||||
const cursor = Presentations.find(selector);
|
||||
const numPages = await cursor.fetchAsync()[0]?.pages?.length ?? 1;
|
||||
const pres = await Presentations.findOneAsync(selector);
|
||||
const numPages = pres?.pages?.length ?? 1;
|
||||
const threshold = EXPORTING_THRESHOLD_PER_SLIDE * numPages;
|
||||
|
||||
const observer = cursor.observe({
|
||||
@ -54,6 +56,6 @@ export default async function exportPresentationToChat(presentationId) {
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method exportPresentationToChat ${err.stack}`);
|
||||
Logger.error(`Exception while invoking method exportPresentation ${err.stack}`);
|
||||
}
|
||||
}
|
@ -52,6 +52,7 @@ export default async function addPresentation(meetingId, podId, presentation) {
|
||||
downloadable: Boolean,
|
||||
removable: Boolean,
|
||||
isInitialPresentation: Boolean,
|
||||
filenameConverted: String,
|
||||
});
|
||||
|
||||
const selector = {
|
||||
@ -71,7 +72,7 @@ export default async function addPresentation(meetingId, podId, presentation) {
|
||||
};
|
||||
|
||||
try {
|
||||
const { insertedId } = await Presentations.upsertAsync(selector, modifier);
|
||||
await Presentations.upsertAsync(selector, modifier);
|
||||
|
||||
await addSlides(meetingId, podId, presentation.id, presentation.pages);
|
||||
|
||||
|
@ -0,0 +1,30 @@
|
||||
import { check } from 'meteor/check';
|
||||
import Presentations from '/imports/api/presentations';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default async function setOriginalUriDownload(meetingId, presentationId, fileURI) {
|
||||
check(meetingId, String);
|
||||
check(presentationId, String);
|
||||
check(fileURI, String);
|
||||
|
||||
const selector = {
|
||||
meetingId,
|
||||
id: presentationId,
|
||||
};
|
||||
|
||||
const modifier = {
|
||||
$set: {
|
||||
originalFileURI: fileURI,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const { numberAffected } = await Presentations.upsertAsync(selector, modifier);
|
||||
|
||||
if (numberAffected) {
|
||||
Logger.info(`Set URI for file ${presentationId} in meeting ${meetingId} URI=${fileURI}`);
|
||||
}
|
||||
} catch (err) {
|
||||
Logger.error(`Could not set URI for file ${presentationId} in meeting ${meetingId} ${err}`);
|
||||
}
|
||||
}
|
@ -51,6 +51,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploader.export.notAccessibleWarning',
|
||||
description: 'used for indicating that a link may be not accessible',
|
||||
},
|
||||
original: {
|
||||
id: 'app.presentationUploader.export.originalLabel',
|
||||
description: 'Label to identify original presentation exported',
|
||||
},
|
||||
annotated: {
|
||||
id: 'app.presentationUploader.export.withAnnotationsLabel',
|
||||
description: 'Label to identify annotated presentation exported',
|
||||
},
|
||||
});
|
||||
|
||||
const setUserSentMessage = (bool) => {
|
||||
@ -328,12 +336,13 @@ const removePackagedClassAttribute = (classnames, attribute) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getExportedPresentationString = (fileURI, filename, intl) => {
|
||||
const warningIcon = `<i class="icon-bbb-warning"></i>`;
|
||||
const getExportedPresentationString = (fileURI, filename, intl, typeOfExport) => {
|
||||
const intlTypeOfExport = typeOfExport === 'Original' ? intlMessages.original : intlMessages.annotated;
|
||||
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=${fileURI} type="application/pdf" rel="noopener, noreferrer" download>${label} ${notAccessibleWarning}</a>`;
|
||||
const name = `<span>${filename}</span>`;
|
||||
const name = `<span>${filename} (${intl.formatMessage(intlTypeOfExport)})</span>`;
|
||||
return `${name}</br>${link}`;
|
||||
};
|
||||
|
||||
|
@ -332,7 +332,7 @@ class TimeWindowChatItem extends PureComponent {
|
||||
<Styled.PresentationChatItem
|
||||
type="presentation"
|
||||
key={messages[0].id}
|
||||
text={getExportedPresentationString(extra.fileURI, extra.filename, intl)}
|
||||
text={getExportedPresentationString(extra.fileURI, extra.filename, intl, extra.typeOfExport)}
|
||||
time={messages[0].time}
|
||||
chatAreaId={chatAreaId}
|
||||
lastReadMessageTime={lastReadMessageTime}
|
||||
|
@ -8,6 +8,7 @@ import { toast } from 'react-toastify';
|
||||
import { Session } from 'meteor/session';
|
||||
import PresentationToolbarContainer from './presentation-toolbar/container';
|
||||
import PresentationMenu from './presentation-menu/container';
|
||||
import DownloadPresentationButton from './download-presentation-button/component';
|
||||
import Styled from './styles';
|
||||
import FullscreenService from '/imports/ui/components/common/fullscreen-button/service';
|
||||
import Icon from '/imports/ui/components/common/icon/component';
|
||||
@ -629,6 +630,23 @@ class Presentation extends PureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
renderPresentationDownload() {
|
||||
const { presentationIsDownloadable, downloadPresentationUri } = this.props;
|
||||
|
||||
if (!presentationIsDownloadable || !downloadPresentationUri) return null;
|
||||
|
||||
const handleDownloadPresentation = () => {
|
||||
window.open(downloadPresentationUri);
|
||||
};
|
||||
|
||||
return (
|
||||
<DownloadPresentationButton
|
||||
handleDownloadPresentation={handleDownloadPresentation}
|
||||
dark
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderPresentationMenu() {
|
||||
const {
|
||||
intl,
|
||||
@ -756,6 +774,7 @@ class Presentation extends PureComponent {
|
||||
}}
|
||||
id="presentationInnerWrapper"
|
||||
>
|
||||
{this.renderPresentationDownload()}
|
||||
<Styled.VisuallyHidden id="currentSlideText">{slideContent}</Styled.VisuallyHidden>
|
||||
{!tldrawIsMounting && currentSlide && this.renderPresentationMenu()}
|
||||
<WhiteboardContainer
|
||||
|
@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
downloadPresentationButton: {
|
||||
id: 'app.downloadPresentationButton.label',
|
||||
description: 'Download presentation label',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
handleDownloadPresentation: PropTypes.func.isRequired,
|
||||
dark: PropTypes.bool,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
dark: false,
|
||||
};
|
||||
|
||||
const DownloadPresentationButton = ({
|
||||
intl,
|
||||
handleDownloadPresentation,
|
||||
dark,
|
||||
}) => (
|
||||
<Styled.ButtonWrapper theme={dark ? 'dark' : 'light'}>
|
||||
<Styled.DownloadButton
|
||||
data-test="presentationDownload"
|
||||
color="default"
|
||||
icon="template_download"
|
||||
size="sm"
|
||||
onClick={handleDownloadPresentation}
|
||||
label={intl.formatMessage(intlMessages.downloadPresentationButton)}
|
||||
hideLabel
|
||||
/>
|
||||
</Styled.ButtonWrapper>
|
||||
);
|
||||
|
||||
DownloadPresentationButton.propTypes = propTypes;
|
||||
DownloadPresentationButton.defaultProps = defaultProps;
|
||||
|
||||
export default injectIntl(DownloadPresentationButton);
|
@ -0,0 +1,74 @@
|
||||
import styled from 'styled-components';
|
||||
import Button from '/imports/ui/components/common/button/component';
|
||||
import {
|
||||
colorWhite,
|
||||
colorBlack,
|
||||
colorTransparent,
|
||||
} from '/imports/ui/stylesheets/styled-components/palette';
|
||||
|
||||
const DownloadButton = styled(Button)`
|
||||
&,
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: ${colorTransparent} !important;
|
||||
border: none !important;
|
||||
|
||||
i {
|
||||
border: none !important;
|
||||
background-color: ${colorTransparent} !important;
|
||||
}
|
||||
}
|
||||
|
||||
padding: 5px;
|
||||
|
||||
&:hover {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const ButtonWrapper = styled.div`
|
||||
position: absolute;
|
||||
right: auto;
|
||||
left: 0;
|
||||
background-color: ${colorTransparent};
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
z-index: 2;
|
||||
margin: 2px;
|
||||
bottom: 0;
|
||||
|
||||
[dir="rtl"] & {
|
||||
right: 0;
|
||||
left : auto;
|
||||
}
|
||||
|
||||
[class*="presentationZoomControls"] & {
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
${({ theme }) => theme === 'dark' && `
|
||||
background-color: rgba(0,0,0,.3) !important;
|
||||
|
||||
& > button i {
|
||||
color: ${colorWhite} !important;
|
||||
}
|
||||
`}
|
||||
|
||||
${({ theme }) => theme === 'light' && `
|
||||
background-color: ${colorTransparent} !important;
|
||||
|
||||
& > button i {
|
||||
color: ${colorBlack} !important;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export default {
|
||||
DownloadButton,
|
||||
ButtonWrapper,
|
||||
};
|
@ -12,6 +12,7 @@ import { notify } from '/imports/ui/services/notification';
|
||||
import { toast } from 'react-toastify';
|
||||
import { registerTitleView, unregisterTitleView } from '/imports/utils/dom-utils';
|
||||
import Styled from './styles';
|
||||
import PresentationDownloadDropdown from './presentation-download-dropdown/component';
|
||||
import Settings from '/imports/ui/services/settings';
|
||||
import Radio from '/imports/ui/components/common/radio/component';
|
||||
import { unique } from 'radash';
|
||||
@ -46,7 +47,8 @@ const propTypes = {
|
||||
presentationUploadExternalUrl: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
isPresenter: PropTypes.bool.isRequired,
|
||||
exportPresentationToChat: PropTypes.func.isRequired,
|
||||
exportPresentation: PropTypes.func.isRequired,
|
||||
hasAnnotations: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
@ -308,6 +310,10 @@ const intlMessages = defineMessages({
|
||||
id: 'app.presentationUploader.export.linkAvailable',
|
||||
description: 'download presentation link available on public chat',
|
||||
},
|
||||
downloadButtonAvailable: {
|
||||
id: 'app.presentationUploader.export.downloadButtonAvailable',
|
||||
description: 'download presentation link available on public chat',
|
||||
},
|
||||
});
|
||||
|
||||
const EXPORT_STATUSES = {
|
||||
@ -341,7 +347,7 @@ class PresentationUploader extends Component {
|
||||
this.handleDismiss = this.handleDismiss.bind(this);
|
||||
this.handleRemove = this.handleRemove.bind(this);
|
||||
this.handleCurrentChange = this.handleCurrentChange.bind(this);
|
||||
this.handleSendToChat = this.handleSendToChat.bind(this);
|
||||
this.handleDownloadingOfPresentation = this.handleDownloadingOfPresentation.bind(this);
|
||||
// renders
|
||||
this.renderDropzone = this.renderDropzone.bind(this);
|
||||
this.renderExternalUpload = this.renderExternalUpload.bind(this);
|
||||
@ -355,6 +361,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);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@ -405,6 +412,11 @@ class PresentationUploader extends Component {
|
||||
modPresentation.isCurrent = currentPropPres.isCurrent;
|
||||
}
|
||||
|
||||
if (currentPropPres?.isDownloadable !== prevPropPres?.isDownloadable) {
|
||||
presentation.isDownloadable = currentPropPres.isDownloadable;
|
||||
shouldUpdateState = true;
|
||||
}
|
||||
|
||||
modPresentation.conversion = currentPropPres.conversion;
|
||||
modPresentation.isRemovable = currentPropPres.isRemovable;
|
||||
|
||||
@ -647,18 +659,26 @@ class PresentationUploader extends Component {
|
||||
return null;
|
||||
}
|
||||
|
||||
handleToggleDownloadable(item) {
|
||||
const { dispatchTogglePresentationDownloadable } = this.props;
|
||||
|
||||
const oldDownloadableState = item.isDownloadable;
|
||||
|
||||
dispatchTogglePresentationDownloadable(item, !oldDownloadableState);
|
||||
}
|
||||
|
||||
handleDismiss() {
|
||||
const { presentations } = this.state;
|
||||
const { presentations: propPresentations } = this.props;
|
||||
|
||||
const ids = new Set(propPresentations.map((d) => d.id));
|
||||
|
||||
const filteredPresentations = presentations.filter((d) => {
|
||||
return !ids.has(d.id) && (d.upload.done || d.upload.progress !== 0)});
|
||||
const isThereStateCurrentPres = filteredPresentations.some(p => p.isCurrent);
|
||||
const filteredPresentations = presentations.filter((d) => !ids.has(d.id)
|
||||
&& (d.upload.done || d.upload.progress !== 0));
|
||||
const isThereStateCurrentPres = filteredPresentations.some((p) => p.isCurrent);
|
||||
const merged = [
|
||||
...filteredPresentations,
|
||||
...propPresentations.filter(p => {
|
||||
...propPresentations.filter((p) => {
|
||||
if (isThereStateCurrentPres) {
|
||||
p.isCurrent = false;
|
||||
}
|
||||
@ -671,9 +691,9 @@ class PresentationUploader extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
handleSendToChat(item) {
|
||||
handleDownloadingOfPresentation(item, type) {
|
||||
const {
|
||||
exportPresentationToChat,
|
||||
exportPresentation,
|
||||
intl,
|
||||
} = this.props;
|
||||
|
||||
@ -681,14 +701,20 @@ class PresentationUploader extends Component {
|
||||
this.deepMergeUpdateFileKey(item.id, 'exportation', exportation);
|
||||
|
||||
if (exportation.status === EXPORT_STATUSES.EXPORTED && stopped) {
|
||||
notify(intl.formatMessage(intlMessages.linkAvailable, { 0: item.filename }), 'success');
|
||||
if (type === 'Original') {
|
||||
if (!item.isDownloadable) {
|
||||
notify(intl.formatMessage(intlMessages.downloadButtonAvailable, { 0: item.filename }), 'success');
|
||||
}
|
||||
} else {
|
||||
notify(intl.formatMessage(intlMessages.linkAvailable, { 0: item.filename }), 'success');
|
||||
}
|
||||
}
|
||||
|
||||
if ([
|
||||
EXPORT_STATUSES.RUNNING,
|
||||
EXPORT_STATUSES.COLLECTING,
|
||||
EXPORT_STATUSES.PROCESSING,
|
||||
].includes(exportation.status)) {
|
||||
].includes(exportation.status) && type !== 'Original') {
|
||||
this.setState((prevState) => {
|
||||
prevState.presExporting.add(item.id);
|
||||
return {
|
||||
@ -723,9 +749,7 @@ class PresentationUploader extends Component {
|
||||
}
|
||||
};
|
||||
|
||||
exportPresentationToChat(item.id, observer);
|
||||
|
||||
Session.set('showUploadPresentationView', false);
|
||||
exportPresentation(item.id, observer, type);
|
||||
}
|
||||
|
||||
getPresentationsToShow() {
|
||||
@ -972,6 +996,7 @@ class PresentationUploader extends Component {
|
||||
selectedToBeNextCurrent,
|
||||
allowDownloadable,
|
||||
renderPresentationItemStatus,
|
||||
hasAnnotations,
|
||||
} = this.props;
|
||||
|
||||
const isActualCurrent = selectedToBeNextCurrent
|
||||
@ -988,14 +1013,14 @@ class PresentationUploader extends Component {
|
||||
|
||||
const { animations } = Settings.application;
|
||||
|
||||
const { isRemovable, exportation: { status } } = item;
|
||||
const { isRemovable, exportation: { status }, isDownloadable } = item;
|
||||
|
||||
const isExporting = status === 'RUNNING';
|
||||
|
||||
const shouldDisableExportButton = isExporting
|
||||
const shouldDisableExportButton = (isExporting
|
||||
|| !item.conversion.done
|
||||
|| hasError
|
||||
|| disableActions;
|
||||
|| disableActions) && !item.conversion?.done;
|
||||
|
||||
const formattedDownloadLabel = isExporting
|
||||
? intl.formatMessage(intlMessages.exporting)
|
||||
@ -1003,6 +1028,7 @@ class PresentationUploader extends Component {
|
||||
|
||||
const formattedDownloadAriaLabel = `${formattedDownloadLabel} ${item.filename}`;
|
||||
|
||||
const hasAnyAnnotation = hasAnnotations(item.id);
|
||||
return (
|
||||
<Styled.PresentationItem
|
||||
key={item.id}
|
||||
@ -1040,18 +1066,22 @@ class PresentationUploader extends Component {
|
||||
<Styled.TableItemStatus colSpan={hasError ? 2 : 0}>
|
||||
{renderPresentationItemStatus(item, intl)}
|
||||
</Styled.TableItemStatus>
|
||||
{hasError ? null : (
|
||||
{
|
||||
hasError ? null : (
|
||||
<Styled.TableItemActions notDownloadable={!allowDownloadable}>
|
||||
{allowDownloadable ? (
|
||||
<Styled.DownloadButton
|
||||
<PresentationDownloadDropdown
|
||||
hasAnnotations={hasAnyAnnotation}
|
||||
disabled={shouldDisableExportButton}
|
||||
label={intl.formatMessage(intlMessages.export)}
|
||||
data-test="exportPresentationToPublicChat"
|
||||
data-test="exportPresentation"
|
||||
aria-label={formattedDownloadAriaLabel}
|
||||
size="sm"
|
||||
color="primary"
|
||||
onClick={() => this.handleSendToChat(item)}
|
||||
animations={animations}
|
||||
isDownloadable={isDownloadable}
|
||||
handleToggleDownloadable={this.handleToggleDownloadable}
|
||||
item={item}
|
||||
closeModal={() => Session.set('showUploadPresentationView', false)}
|
||||
handleDownloadingOfPresentation={(type) => this
|
||||
.handleDownloadingOfPresentation(item, type)}
|
||||
/>
|
||||
) : null}
|
||||
{isRemovable ? (
|
||||
|
@ -9,6 +9,7 @@ import PresentationUploader from './component';
|
||||
import { UsersContext } from '/imports/ui/components/components-data/users-context/context';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { isDownloadPresentationWithAnnotationsEnabled, isPresentationEnabled } from '/imports/ui/services/features';
|
||||
import { hasAnnotations } from '/imports/ui/components/whiteboard/service';
|
||||
|
||||
const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
|
||||
|
||||
@ -32,7 +33,7 @@ export default withTracker(() => {
|
||||
dispatchDisableDownloadable,
|
||||
dispatchEnableDownloadable,
|
||||
dispatchTogglePresentationDownloadable,
|
||||
exportPresentationToChat,
|
||||
exportPresentation,
|
||||
} = Service;
|
||||
const isOpen = isPresentationEnabled() && (Session.get('showUploadPresentationView') || false);
|
||||
|
||||
@ -51,10 +52,11 @@ export default withTracker(() => {
|
||||
dispatchDisableDownloadable,
|
||||
dispatchEnableDownloadable,
|
||||
dispatchTogglePresentationDownloadable,
|
||||
exportPresentationToChat,
|
||||
exportPresentation,
|
||||
isOpen,
|
||||
selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null,
|
||||
externalUploadData: Service.getExternalUploadData(),
|
||||
handleFiledrop: Service.handleFiledrop,
|
||||
hasAnnotations,
|
||||
};
|
||||
})(PresentationUploaderContainer);
|
||||
|
@ -0,0 +1,173 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
import BBBMenu from '/imports/ui/components/common/menu/component';
|
||||
import { uniqueId } from '/imports/utils/string-utils';
|
||||
import Trigger from '/imports/ui/components/common/control-header/right/component';
|
||||
import PresentationDownloadDropdownWrapper from './presentation-download-dropdown-wrapper/component';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
enableOriginalPresentationDownload: {
|
||||
id: 'app.presentationUploader.enableOriginalPresentationDownload',
|
||||
description: 'Send original presentation to chat',
|
||||
},
|
||||
disableOriginalPresentationDownload: {
|
||||
id: 'app.presentationUploader.disableOriginalPresentationDownload',
|
||||
description: 'Send original presentation to chat',
|
||||
},
|
||||
sendAnnotatedDocument: {
|
||||
id: 'app.presentationUploader.exportAnnotatedPresentation',
|
||||
description: 'Send presentation to chat with annotations label',
|
||||
},
|
||||
copySuccess: {
|
||||
id: 'app.chat.copySuccess',
|
||||
description: 'aria success alert',
|
||||
},
|
||||
copyErr: {
|
||||
id: 'app.chat.copyErr',
|
||||
description: 'aria error alert',
|
||||
},
|
||||
options: {
|
||||
id: 'app.presentationUploader.dropdownExportOptions',
|
||||
description: 'Chat Options',
|
||||
},
|
||||
});
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
handleDownloadingOfPresentation: PropTypes.func.isRequired,
|
||||
handleToggleDownloadable: PropTypes.func.isRequired,
|
||||
isDownloadable: PropTypes.bool.isRequired,
|
||||
item: PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
filename: PropTypes.string.isRequired,
|
||||
isCurrent: PropTypes.bool.isRequired,
|
||||
temporaryPresentationId: PropTypes.string.isRequired,
|
||||
isDownloadable: PropTypes.bool.isRequired,
|
||||
isRemovable: PropTypes.bool.isRequired,
|
||||
conversion: PropTypes.shape,
|
||||
upload: PropTypes.shape,
|
||||
exportation: PropTypes.shape,
|
||||
uploadTimestamp: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
closeModal: PropTypes.func.isRequired,
|
||||
hasAnnotations: PropTypes.bool.isRequired,
|
||||
isRTL: PropTypes.bool.isRequired,
|
||||
disabled: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
class PresentationDownloadDropdown extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.actionsKey = [
|
||||
uniqueId('action-item-'),
|
||||
uniqueId('action-item-'),
|
||||
uniqueId('action-item-'),
|
||||
];
|
||||
}
|
||||
|
||||
getAvailableActions() {
|
||||
const {
|
||||
intl,
|
||||
handleDownloadingOfPresentation,
|
||||
handleToggleDownloadable,
|
||||
isDownloadable,
|
||||
item,
|
||||
closeModal,
|
||||
hasAnnotations,
|
||||
} = this.props;
|
||||
|
||||
this.menuItems = [];
|
||||
|
||||
const toggleDownloadOriginalPresentation = (enableDownload) => {
|
||||
handleToggleDownloadable(item);
|
||||
if (enableDownload) {
|
||||
handleDownloadingOfPresentation('Original');
|
||||
}
|
||||
closeModal();
|
||||
};
|
||||
|
||||
if (!isDownloadable) {
|
||||
this.menuItems.push(
|
||||
{
|
||||
key: this.actionsKey[0],
|
||||
dataTest: 'enableOriginalPresentationDownload',
|
||||
label: intl.formatMessage(intlMessages.enableOriginalPresentationDownload),
|
||||
onClick: () => toggleDownloadOriginalPresentation(true),
|
||||
},
|
||||
);
|
||||
} else {
|
||||
this.menuItems.push(
|
||||
{
|
||||
key: this.actionsKey[0],
|
||||
dataTest: 'disableOriginalPresentationDownload',
|
||||
label: intl.formatMessage(intlMessages.disableOriginalPresentationDownload),
|
||||
onClick: () => toggleDownloadOriginalPresentation(false),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if (hasAnnotations) {
|
||||
this.menuItems.push(
|
||||
{
|
||||
key: this.actionsKey[1],
|
||||
id: 'sendAnnotatedDocument',
|
||||
dataTest: 'sendAnnotatedDocument',
|
||||
label: intl.formatMessage(intlMessages.sendAnnotatedDocument),
|
||||
onClick: () => {
|
||||
closeModal();
|
||||
handleDownloadingOfPresentation('Annotated');
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return this.menuItems;
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
isRTL,
|
||||
disabled,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PresentationDownloadDropdownWrapper
|
||||
disabled={disabled}
|
||||
>
|
||||
<BBBMenu
|
||||
trigger={
|
||||
(
|
||||
<Trigger
|
||||
data-test="presentationOptionsDownload"
|
||||
icon="more"
|
||||
label={intl.formatMessage(intlMessages.options)}
|
||||
aria-label={intl.formatMessage(intlMessages.options)}
|
||||
onClick={() => null}
|
||||
/>
|
||||
)
|
||||
}
|
||||
opts={{
|
||||
id: 'presentation-download-dropdown',
|
||||
keepMounted: true,
|
||||
transitionDuration: 0,
|
||||
elevation: 2,
|
||||
getContentAnchorEl: null,
|
||||
fullwidth: 'true',
|
||||
anchorOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
|
||||
transformOrigin: { vertical: 'top', horizontal: isRTL ? 'right' : 'left' },
|
||||
}}
|
||||
actions={this.getAvailableActions()}
|
||||
/>
|
||||
</PresentationDownloadDropdownWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PresentationDownloadDropdown.propTypes = propTypes;
|
||||
|
||||
export default injectIntl(PresentationDownloadDropdown);
|
@ -0,0 +1,110 @@
|
||||
import React, { PureComponent } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Styled from './styles';
|
||||
|
||||
const propTypes = {
|
||||
intl: PropTypes.shape({
|
||||
formatMessage: PropTypes.func.isRequired,
|
||||
}).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') {
|
||||
return eventHandler(...args);
|
||||
}
|
||||
|
||||
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,
|
||||
children,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
PresentationDownloadDropdownWrapper.propTypes = propTypes;
|
||||
|
||||
export default PresentationDownloadDropdownWrapper;
|
@ -0,0 +1,16 @@
|
||||
import styled from 'styled-components';
|
||||
|
||||
const DropdownMenuWrapper = styled.div`
|
||||
display: inline-block;
|
||||
|
||||
&[aria-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: .5;
|
||||
box-shadow: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
DropdownMenuWrapper,
|
||||
};
|
@ -378,7 +378,7 @@ const getExternalUploadData = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const exportPresentationToChat = (presentationId, observer) => {
|
||||
const exportPresentation = (presentationId, observer, type) => {
|
||||
let lastStatus = {};
|
||||
|
||||
Tracker.autorun((c) => {
|
||||
@ -407,7 +407,7 @@ const exportPresentationToChat = (presentationId, observer) => {
|
||||
});
|
||||
});
|
||||
|
||||
makeCall('exportPresentationToChat', presentationId);
|
||||
makeCall('exportPresentation', presentationId, type);
|
||||
};
|
||||
|
||||
function handleFiledrop(files, files2, that, intl, intlMessages) {
|
||||
@ -487,7 +487,7 @@ export default {
|
||||
setPresentation,
|
||||
requestPresentationUploadToken,
|
||||
getExternalUploadData,
|
||||
exportPresentationToChat,
|
||||
exportPresentation,
|
||||
uploadAndConvertPresentation,
|
||||
handleFiledrop,
|
||||
};
|
||||
|
@ -530,50 +530,6 @@ const TableItemActions = styled.td`
|
||||
`}
|
||||
`;
|
||||
|
||||
const DownloadButton = styled(Button)`
|
||||
background: transparent;
|
||||
background-color: transparent;
|
||||
border: 2px solid ${colorBlueLight} !important;
|
||||
border-radius: 4px;
|
||||
color: ${colorBlueLight};
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 80%;
|
||||
|
||||
&:hover {
|
||||
background-color: ${colorOffWhite} !important;
|
||||
color: ${colorBlueLight} !important;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: transparent !important;
|
||||
color: ${colorBlueLight} !important;
|
||||
}
|
||||
|
||||
&:hover:focus {
|
||||
background-color: ${colorOffWhite} !important;
|
||||
color: ${colorBlueLight} !important;
|
||||
}
|
||||
|
||||
&:active:focus {
|
||||
background-color: ${colorOffWhite} !important;
|
||||
color: ${colorBlueLight} !important;
|
||||
filter: brightness(85%) !important;
|
||||
}
|
||||
|
||||
${({ animations }) => animations && `
|
||||
transition: all .25s;
|
||||
`}
|
||||
|
||||
&[aria-disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
opacity: .5;
|
||||
box-shadow: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const ExtraHint = styled.div`
|
||||
margin-top: 1rem;
|
||||
font-weight: bold;
|
||||
@ -702,7 +658,6 @@ export default {
|
||||
StatusInfoSpan,
|
||||
PresentationItem,
|
||||
TableItemActions,
|
||||
DownloadButton,
|
||||
ExtraHint,
|
||||
ExternalUpload,
|
||||
ExternalUploadTitle,
|
||||
|
@ -17,13 +17,7 @@ const downloadPresentationUri = (podId) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const presentationFileName = `${currentPresentation.id}.${currentPresentation.name.split('.').pop()}`;
|
||||
|
||||
const APP = Meteor.settings.public.app;
|
||||
const uri = `${APP.bbbWebBase}/presentation/download/`
|
||||
+ `${currentPresentation.meetingId}/${currentPresentation.id}`
|
||||
+ `?presFilename=${encodeURIComponent(presentationFileName)}`;
|
||||
|
||||
const { originalFileURI: uri } = currentPresentation;
|
||||
return uri;
|
||||
};
|
||||
|
||||
|
@ -212,6 +212,13 @@ const getCurrentWhiteboardId = () => {
|
||||
return currentSlide && currentSlide.id;
|
||||
};
|
||||
|
||||
const hasAnnotations = (presentationId) => {
|
||||
const ann = Annotations.findOne(
|
||||
{ whiteboardId: { $regex: `^${presentationId}` } },
|
||||
);
|
||||
return ann !== undefined;
|
||||
};
|
||||
|
||||
const isMultiUserActive = (whiteboardId) => {
|
||||
const multiUser = getMultiUser(whiteboardId);
|
||||
|
||||
@ -404,5 +411,6 @@ export {
|
||||
changeCurrentSlide,
|
||||
notifyNotAllowedChange,
|
||||
notifyShapeNumberExceeded,
|
||||
hasAnnotations,
|
||||
toggleToolsAnimations,
|
||||
};
|
||||
|
@ -250,8 +250,15 @@
|
||||
"app.presentationUploader.sent": "Sent",
|
||||
"app.presentationUploader.exportingTimeout": "The export is taking too long...",
|
||||
"app.presentationUploader.export": "Send to chat",
|
||||
"app.presentationUploader.exportAnnotatedPresentation": "Send out a download link for the presentation with annotations included",
|
||||
"app.presentationUploader.enableOriginalPresentationDownload": "Enable download of the original presentation",
|
||||
"app.presentationUploader.disableOriginalPresentationDownload": "Disable download of the original presentation",
|
||||
"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.",
|
||||
"app.presentationUploader.export.notAccessibleWarning": "may not be accessibility compliant",
|
||||
"app.presentationUploader.export.originalLabel": "Original",
|
||||
"app.presentationUploader.export.withAnnotationsLabel": "With Annotations",
|
||||
"app.presentationUploader.currentPresentationLabel": "Current presentation",
|
||||
"app.presentationUploder.extraHint": "IMPORTANT: each file may not exceed {0} MB and {1} pages.",
|
||||
"app.presentationUploder.uploadLabel": "Upload",
|
||||
|
@ -338,6 +338,7 @@ class PresentationController {
|
||||
def presId = params.presId
|
||||
def presFilename = params.presFilename
|
||||
def meetingId = params.meetingId
|
||||
def filename = params.filename
|
||||
|
||||
log.debug "Controller: Download request for $presFilename"
|
||||
|
||||
@ -348,7 +349,7 @@ class PresentationController {
|
||||
log.debug "Controller: Sending pdf reply for $presFilename"
|
||||
|
||||
def bytes = pres.readBytes()
|
||||
def responseName = pres.getName();
|
||||
def responseName = filename;
|
||||
def mimeType = grailsMimeUtility.getMimeTypeForURI(responseName)
|
||||
def mimeName = mimeType != null ? mimeType.name : 'application/octet-stream'
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user