add functionality to enable infinite whiteboard
This commit is contained in:
parent
5cc9604a54
commit
687ee36d29
@ -11,6 +11,7 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
|
||||
with PresentationConversionCompletedSysPubMsgHdlr
|
||||
with PdfConversionInvalidErrorSysPubMsgHdlr
|
||||
with SetCurrentPagePubMsgHdlr
|
||||
with SetPageInfiniteCanvasPubMsgHdlr
|
||||
with SetPresenterInDefaultPodInternalMsgHdlr
|
||||
with RemovePresentationPubMsgHdlr
|
||||
with SetPresentationDownloadablePubMsgHdlr
|
||||
|
@ -0,0 +1,33 @@
|
||||
package org.bigbluebutton.core.apps.presentationpod
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.db.PresPageDAO
|
||||
|
||||
trait SetPageInfiniteCanvasPubMsgHdlr extends RightsManagementTrait {
|
||||
this: PresentationPodHdlrs =>
|
||||
|
||||
def handle(
|
||||
msg: SetPageInfiniteCanvasPubMsg, state: MeetingState2x,
|
||||
liveMeeting: LiveMeeting, bus: MessageBus
|
||||
): MeetingState2x = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to set infinite canvas."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
state
|
||||
} else {
|
||||
val pageId = msg.body.pageId
|
||||
val infiniteCanvas = msg.body.infiniteCanvas
|
||||
|
||||
PresPageDAO.updateInfiniteCanvas(pageId, infiniteCanvas)
|
||||
|
||||
state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -23,7 +23,8 @@ case class PresPageDbModel(
|
||||
viewBoxHeight: Double,
|
||||
maxImageWidth: Int,
|
||||
maxImageHeight: Int,
|
||||
uploadCompleted: Boolean
|
||||
uploadCompleted: Boolean,
|
||||
infiniteCanvas: Boolean,
|
||||
)
|
||||
|
||||
class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pres_page") {
|
||||
@ -45,8 +46,9 @@ class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pr
|
||||
val maxImageWidth = column[Int]("maxImageWidth")
|
||||
val maxImageHeight = column[Int]("maxImageHeight")
|
||||
val uploadCompleted = column[Boolean]("uploadCompleted")
|
||||
val infiniteCanvas = column[Boolean]("infiniteCanvas")
|
||||
// val presentation = foreignKey("presentation_fk", presentationId, Presentations)(_.presentationId, onDelete = ForeignKeyAction.Cascade)
|
||||
def * = (pageId, presentationId, num, urlsJson, content, slideRevealed, current, xOffset, yOffset, widthRatio, heightRatio, width, height, viewBoxWidth, viewBoxHeight, maxImageWidth, maxImageHeight, uploadCompleted) <> (PresPageDbModel.tupled, PresPageDbModel.unapply)
|
||||
def * = (pageId, presentationId, num, urlsJson, content, slideRevealed, current, xOffset, yOffset, widthRatio, heightRatio, width, height, viewBoxWidth, viewBoxHeight, maxImageWidth, maxImageHeight, uploadCompleted, infiniteCanvas) <> (PresPageDbModel.tupled, PresPageDbModel.unapply)
|
||||
}
|
||||
|
||||
object PresPageDAO {
|
||||
@ -76,7 +78,8 @@ object PresPageDAO {
|
||||
viewBoxHeight = 1,
|
||||
maxImageWidth = 1440,
|
||||
maxImageHeight = 1080,
|
||||
uploadCompleted = page.converted
|
||||
uploadCompleted = page.converted,
|
||||
infiniteCanvas = page.infiniteCanvas
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -109,4 +112,16 @@ object PresPageDAO {
|
||||
.update((width, height, xOffset, yOffset, widthRatio, heightRatio))
|
||||
)
|
||||
}
|
||||
|
||||
def updateInfiniteCanvas(pageId: String, infiniteCanvas: Boolean) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[PresPageDbTableDef]
|
||||
.filter(_.pageId === pageId)
|
||||
.map(p => p.infiniteCanvas)
|
||||
.update(infiniteCanvas)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated infiniteCanvas on PresPage table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating infiniteCanvas on PresPage: $e")
|
||||
}
|
||||
}
|
||||
}
|
@ -153,34 +153,54 @@ object PresPresentationDAO {
|
||||
))
|
||||
)
|
||||
|
||||
DatabaseConnection.enqueue(DBIO.sequence(
|
||||
for {
|
||||
page <- presentation.pages
|
||||
} yield {
|
||||
TableQuery[PresPageDbTableDef].insertOrUpdate(
|
||||
PresPageDbModel(
|
||||
pageId = page._2.id,
|
||||
presentationId = presentation.id,
|
||||
num = page._2.num,
|
||||
urlsJson = page._2.urls.toJson,
|
||||
content = page._2.content,
|
||||
slideRevealed = page._2.current,
|
||||
current = page._2.current,
|
||||
xOffset = page._2.xOffset,
|
||||
yOffset = page._2.yOffset,
|
||||
widthRatio = page._2.widthRatio,
|
||||
heightRatio = page._2.heightRatio,
|
||||
width = page._2.width,
|
||||
height = page._2.height,
|
||||
viewBoxWidth = 1,
|
||||
viewBoxHeight = 1,
|
||||
maxImageWidth = 1440,
|
||||
maxImageHeight = 1080,
|
||||
uploadCompleted = page._2.converted
|
||||
)
|
||||
def updatePages(presentation: PresentationInPod) = {
|
||||
DatabaseConnection.enqueue(
|
||||
TableQuery[PresPresentationDbTableDef]
|
||||
.filter(_.presentationId === presentation.id)
|
||||
.map(p => (p.downloadFileExtension, p.uploadInProgress, p.uploadCompleted, p.totalPages))
|
||||
.update((
|
||||
presentation.downloadFileExtension match {
|
||||
case "" => None
|
||||
case downloadFileExtension => Some(downloadFileExtension)
|
||||
},
|
||||
!presentation.uploadCompleted,
|
||||
presentation.uploadCompleted,
|
||||
presentation.numPages
|
||||
))
|
||||
)
|
||||
|
||||
DatabaseConnection.db.run(DBIO.sequence(
|
||||
for {
|
||||
page <- presentation.pages
|
||||
} yield {
|
||||
TableQuery[PresPageDbTableDef].insertOrUpdate(
|
||||
PresPageDbModel(
|
||||
pageId = page._2.id,
|
||||
presentationId = presentation.id,
|
||||
num = page._2.num,
|
||||
urlsJson = page._2.urls.toJson,
|
||||
content = page._2.content,
|
||||
slideRevealed = page._2.current,
|
||||
current = page._2.current,
|
||||
xOffset = page._2.xOffset,
|
||||
yOffset = page._2.yOffset,
|
||||
widthRatio = page._2.widthRatio,
|
||||
heightRatio = page._2.heightRatio,
|
||||
width = page._2.width,
|
||||
height = page._2.height,
|
||||
viewBoxWidth = 1,
|
||||
viewBoxHeight = 1,
|
||||
maxImageWidth = 1440,
|
||||
maxImageHeight = 1080,
|
||||
uploadCompleted = page._2.converted,
|
||||
infiniteCanvas = page._2.infiniteCanvas
|
||||
)
|
||||
}
|
||||
).transactionally)
|
||||
)
|
||||
}
|
||||
).transactionally).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on PresentationPage table!")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating PresentationPage: $e")
|
||||
}
|
||||
|
||||
//Set current
|
||||
if (presentation.current) {
|
||||
|
@ -30,7 +30,8 @@ case class PresentationPage(
|
||||
heightRatio: Double = 100D,
|
||||
width: Double = 1440D,
|
||||
height: Double = 1080D,
|
||||
converted: Boolean = false
|
||||
converted: Boolean = false,
|
||||
infiniteCanvas: Boolean = false,
|
||||
)
|
||||
|
||||
object PresentationInPod {
|
||||
|
@ -287,6 +287,8 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[SetCurrentPresentationPubMsg](envelope, jsonNode)
|
||||
case SetCurrentPagePubMsg.NAME =>
|
||||
routeGenericMsg[SetCurrentPagePubMsg](envelope, jsonNode)
|
||||
case SetPageInfiniteCanvasPubMsg.NAME =>
|
||||
routeGenericMsg[SetPageInfiniteCanvasPubMsg](envelope, jsonNode)
|
||||
case ResizeAndMovePagePubMsg.NAME =>
|
||||
routeGenericMsg[ResizeAndMovePagePubMsg](envelope, jsonNode)
|
||||
case SlideResizedPubMsg.NAME =>
|
||||
|
@ -36,7 +36,6 @@ import org.apache.pekko.actor.Props
|
||||
import org.apache.pekko.actor.OneForOneStrategy
|
||||
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPathAsBooleanOrElse, getConfigPropertyValueByPathAsIntOrElse, getConfigPropertyValueByPathAsStringOrElse }
|
||||
import org.bigbluebutton.common2.msgs
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import org.bigbluebutton.core.apps.layout.LayoutApp2x
|
||||
import org.bigbluebutton.core.apps.meeting.{ SyncGetMeetingInfoRespMsgHdlr, ValidateConnAuthTokenSysMsgHdlr }
|
||||
@ -633,6 +632,7 @@ class MeetingActor(
|
||||
case m: SetCurrentPagePubMsg =>
|
||||
state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
updateUserLastActivity(m.header.userId)
|
||||
case m: SetPageInfiniteCanvasPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: RemovePresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
|
||||
|
@ -96,6 +96,8 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
|
||||
msgSender.send(fromAkkaAppsPresRedisChannel, json)
|
||||
case SetCurrentPageEvtMsg.NAME =>
|
||||
msgSender.send(fromAkkaAppsPresRedisChannel, json)
|
||||
case SetPageInfiniteCanvasEvtMsg.NAME =>
|
||||
msgSender.send(fromAkkaAppsPresRedisChannel, json)
|
||||
case ResizeAndMovePageEvtMsg.NAME =>
|
||||
msgSender.send(fromAkkaAppsPresRedisChannel, json)
|
||||
case RemovePresentationEvtMsg.NAME =>
|
||||
|
@ -23,6 +23,10 @@ object SetCurrentPagePubMsg { val NAME = "SetCurrentPagePubMsg" }
|
||||
case class SetCurrentPagePubMsg(header: BbbClientMsgHeader, body: SetCurrentPagePubMsgBody) extends StandardMsg
|
||||
case class SetCurrentPagePubMsgBody(podId: String, presentationId: String, pageId: String)
|
||||
|
||||
object SetPageInfiniteCanvasPubMsg { val NAME = "SetPageInfiniteCanvasPubMsg" }
|
||||
case class SetPageInfiniteCanvasPubMsg(header: BbbClientMsgHeader, body: SetPageInfiniteCanvasPubMsgBody) extends StandardMsg
|
||||
case class SetPageInfiniteCanvasPubMsgBody(pageId: String, infiniteCanvas: Boolean)
|
||||
|
||||
object RemovePresentationPubMsg { val NAME = "RemovePresentationPubMsg" }
|
||||
case class RemovePresentationPubMsg(header: BbbClientMsgHeader, body: RemovePresentationPubMsgBody) extends StandardMsg
|
||||
case class RemovePresentationPubMsgBody(podId: String, presentationId: String)
|
||||
@ -325,6 +329,10 @@ object SetCurrentPageEvtMsg { val NAME = "SetCurrentPageEvtMsg" }
|
||||
case class SetCurrentPageEvtMsg(header: BbbClientMsgHeader, body: SetCurrentPageEvtMsgBody) extends BbbCoreMsg
|
||||
case class SetCurrentPageEvtMsgBody(podId: String, presentationId: String, pageId: String)
|
||||
|
||||
object SetPageInfiniteCanvasEvtMsg { val NAME = "SetPageInfiniteCanvasEvtMsg" }
|
||||
case class SetPageInfiniteCanvasEvtMsg(header: BbbClientMsgHeader, body: SetPageInfiniteCanvasEvtMsgBody) extends BbbCoreMsg
|
||||
case class SetPageInfiniteCanvasEvtMsgBody(pageId: String, infiniteCanvas: Boolean)
|
||||
|
||||
object SetPresenterInPodRespMsg { val NAME = "SetPresenterInPodRespMsg" }
|
||||
case class SetPresenterInPodRespMsg(header: BbbClientMsgHeader, body: SetPresenterInPodRespMsgBody) extends StandardMsg
|
||||
case class SetPresenterInPodRespMsgBody(podId: String, nextPresenterId: String)
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { RedisMessage } from '../types';
|
||||
import {throwErrorIfInvalidInput, throwErrorIfNotPresenter} from "../imports/validation";
|
||||
|
||||
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
|
||||
throwErrorIfNotPresenter(sessionVariables);
|
||||
throwErrorIfInvalidInput(input,
|
||||
[
|
||||
{name: 'infiniteCanvas', type: 'boolean', required: true},
|
||||
{name: 'pageId', type: 'string', required: true},
|
||||
]
|
||||
)
|
||||
|
||||
const eventName = `SetPageInfiniteCanvasPubMsg`;
|
||||
|
||||
const routing = {
|
||||
meetingId: sessionVariables['x-hasura-meetingid'] as string,
|
||||
userId: sessionVariables['x-hasura-userid'] as string
|
||||
};
|
||||
|
||||
const header = {
|
||||
name: eventName,
|
||||
meetingId: routing.meetingId,
|
||||
userId: routing.userId
|
||||
};
|
||||
|
||||
const body = {
|
||||
pageId: input.pageId,
|
||||
infiniteCanvas: input.infiniteCanvas,
|
||||
};
|
||||
|
||||
return { eventName, routing, header, body };
|
||||
}
|
@ -1207,7 +1207,8 @@ CREATE TABLE "pres_page" (
|
||||
"viewBoxHeight" NUMERIC,
|
||||
"maxImageWidth" integer,
|
||||
"maxImageHeight" integer,
|
||||
"uploadCompleted" boolean
|
||||
"uploadCompleted" boolean,
|
||||
"infiniteCanvas" boolean
|
||||
);
|
||||
CREATE INDEX "idx_pres_page_presentationId" ON "pres_page"("presentationId");
|
||||
CREATE INDEX "idx_pres_page_presentationId_curr" ON "pres_page"("presentationId") where "current" is true;
|
||||
@ -1265,7 +1266,8 @@ SELECT pres_presentation."meetingId",
|
||||
(pres_page."height" * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledHeight",
|
||||
(pres_page."width" * pres_page."widthRatio" / 100 * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledViewBoxWidth",
|
||||
(pres_page."height" * pres_page."heightRatio" / 100 * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledViewBoxHeight",
|
||||
pres_page."uploadCompleted"
|
||||
pres_page."uploadCompleted",
|
||||
pres_page."infiniteCanvas"
|
||||
FROM pres_page
|
||||
JOIN pres_presentation ON pres_presentation."presentationId" = pres_page."presentationId";
|
||||
|
||||
@ -1297,7 +1299,8 @@ SELECT pres_presentation."meetingId",
|
||||
(pres_page."width" * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledWidth",
|
||||
(pres_page."height" * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledHeight",
|
||||
(pres_page."width" * pres_page."widthRatio" / 100 * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledViewBoxWidth",
|
||||
(pres_page."height" * pres_page."heightRatio" / 100 * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledViewBoxHeight"
|
||||
(pres_page."height" * pres_page."heightRatio" / 100 * LEAST(pres_page."maxImageWidth" / pres_page."width", pres_page."maxImageHeight" / pres_page."height")) AS "scaledViewBoxHeight",
|
||||
pres_page."infiniteCanvas"
|
||||
FROM pres_presentation
|
||||
JOIN pres_page ON pres_presentation."presentationId" = pres_page."presentationId" AND pres_page."current" IS TRUE
|
||||
and pres_presentation."current" IS TRUE;
|
||||
|
@ -357,6 +357,13 @@ type Mutation {
|
||||
): Boolean
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
presentationSetPageInfiniteCanvas(
|
||||
infiniteCanvas: Boolean!
|
||||
pageId: String!
|
||||
): Boolean
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
presentationSetRenderedInToast(
|
||||
presentationId: String!
|
||||
|
@ -306,6 +306,12 @@ actions:
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
- name: presentationSetPageInfiniteCanvas
|
||||
definition:
|
||||
kind: synchronous
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
- name: presentationSetRenderedInToast
|
||||
definition:
|
||||
kind: synchronous
|
||||
|
@ -39,6 +39,7 @@ select_permissions:
|
||||
- widthRatio
|
||||
- xOffset
|
||||
- yOffset
|
||||
- infiniteCanvas
|
||||
filter:
|
||||
meetingId:
|
||||
_eq: X-Hasura-PresenterInMeeting
|
||||
|
@ -37,6 +37,7 @@ select_permissions:
|
||||
- widthRatio
|
||||
- xOffset
|
||||
- yOffset
|
||||
- infiniteCanvas
|
||||
filter:
|
||||
meetingId:
|
||||
_eq: X-Hasura-MeetingId
|
||||
|
@ -694,6 +694,7 @@ export interface Whiteboard {
|
||||
maxStickyNoteLength: number
|
||||
maxNumberOfAnnotations: number
|
||||
annotations: Annotations
|
||||
allowInfiniteCanvas: boolean
|
||||
styles: Styles
|
||||
toolbar: Toolbar
|
||||
}
|
||||
|
@ -595,6 +595,7 @@ class Presentation extends PureComponent {
|
||||
totalPages,
|
||||
userIsPresenter,
|
||||
hasPoll,
|
||||
currentPresentationPage,
|
||||
} = this.props;
|
||||
const { zoom, isPanning } = this.state;
|
||||
|
||||
@ -619,6 +620,7 @@ class Presentation extends PureComponent {
|
||||
layoutContextDispatch,
|
||||
presentationIsOpen,
|
||||
userIsPresenter,
|
||||
currentPresentationPage,
|
||||
}}
|
||||
setIsPanning={this.setIsPanning}
|
||||
isPanning={isPanning}
|
||||
|
@ -125,6 +125,7 @@ const PresentationContainer = (props) => {
|
||||
num: currentPresentationPage?.num,
|
||||
presentationId: currentPresentationPage?.presentationId,
|
||||
svgUri: slideSvgUrl,
|
||||
infiniteCanvas: currentPresentationPage.infiniteCanvas,
|
||||
} : null;
|
||||
|
||||
let slidePosition;
|
||||
@ -201,42 +202,43 @@ const PresentationContainer = (props) => {
|
||||
return (
|
||||
<Presentation
|
||||
{
|
||||
...{
|
||||
layoutContextDispatch,
|
||||
numCameras,
|
||||
...props,
|
||||
userIsPresenter,
|
||||
presentationBounds: presentation,
|
||||
fullscreenContext,
|
||||
fullscreenElementId,
|
||||
isMobile: deviceType === DEVICE_TYPE.MOBILE,
|
||||
isIphone,
|
||||
currentSlide,
|
||||
slidePosition,
|
||||
downloadPresentationUri: `${APP_CONFIG.bbbWebBase}/${currentPresentationPage?.downloadFileUri}`,
|
||||
multiUser: (multiUserData.hasAccess || multiUserData.active) && presentationIsOpen,
|
||||
presentationIsDownloadable: currentPresentationPage?.downloadable,
|
||||
mountPresentation: !!currentSlide,
|
||||
currentPresentationId: currentPresentationPage?.presentationId,
|
||||
totalPages: currentPresentationPage?.totalPages || 0,
|
||||
notify,
|
||||
zoomSlide,
|
||||
publishedPoll: poll?.published || false,
|
||||
restoreOnUpdate: getFromUserSettings(
|
||||
'bbb_force_restore_presentation_on_new_events',
|
||||
window.meetingClientSettings.public.presentation.restoreOnUpdate,
|
||||
),
|
||||
addWhiteboardGlobalAccess: getUsers,
|
||||
removeWhiteboardGlobalAccess,
|
||||
multiUserSize: multiUserData.size,
|
||||
isViewersAnnotationsLocked,
|
||||
setPresentationIsOpen: MediaService.setPresentationIsOpen,
|
||||
isDefaultPresentation: currentPresentationPage?.isDefaultPresentation,
|
||||
presentationName: currentPresentationPage?.presentationName,
|
||||
presentationAreaSize,
|
||||
currentUser,
|
||||
hasPoll,
|
||||
}
|
||||
...{
|
||||
layoutContextDispatch,
|
||||
numCameras,
|
||||
...props,
|
||||
userIsPresenter,
|
||||
presentationBounds: presentation,
|
||||
fullscreenContext,
|
||||
fullscreenElementId,
|
||||
isMobile: deviceType === DEVICE_TYPE.MOBILE,
|
||||
isIphone,
|
||||
currentSlide,
|
||||
slidePosition,
|
||||
downloadPresentationUri: `${APP_CONFIG.bbbWebBase}/${currentPresentationPage?.downloadFileUri}`,
|
||||
multiUser: (multiUserData.hasAccess || multiUserData.active) && presentationIsOpen,
|
||||
presentationIsDownloadable: currentPresentationPage?.downloadable,
|
||||
mountPresentation: !!currentSlide,
|
||||
currentPresentationId: currentPresentationPage?.presentationId,
|
||||
totalPages: currentPresentationPage?.totalPages || 0,
|
||||
notify,
|
||||
zoomSlide,
|
||||
publishedPoll: poll?.published || false,
|
||||
restoreOnUpdate: getFromUserSettings(
|
||||
'bbb_force_restore_presentation_on_new_events',
|
||||
window.meetingClientSettings.public.presentation.restoreOnUpdate,
|
||||
),
|
||||
addWhiteboardGlobalAccess: getUsers,
|
||||
removeWhiteboardGlobalAccess,
|
||||
multiUserSize: multiUserData.size,
|
||||
isViewersAnnotationsLocked,
|
||||
setPresentationIsOpen: MediaService.setPresentationIsOpen,
|
||||
isDefaultPresentation: currentPresentationPage?.isDefaultPresentation,
|
||||
presentationName: currentPresentationPage?.presentationName,
|
||||
presentationAreaSize,
|
||||
currentUser,
|
||||
hasPoll,
|
||||
currentPresentationPage,
|
||||
}
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
@ -100,6 +100,15 @@ export const PRESENTATION_PUBLISH_CURSOR = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_SET_PAGE_INFINITE_CANVAS = gql`
|
||||
mutation PresentationSetPageInfiniteCanvas($pageId: String!, $infiniteCanvas: Boolean!) {
|
||||
presentationSetPageInfiniteCanvas(
|
||||
pageId: $pageId,
|
||||
infiniteCanvas: $infiniteCanvas
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
PRESENTATION_SET_ZOOM,
|
||||
PRESENTATION_SET_WRITERS,
|
||||
@ -111,4 +120,5 @@ export default {
|
||||
PRES_ANNOTATION_DELETE,
|
||||
PRES_ANNOTATION_SUBMIT,
|
||||
PRESENTATION_PUBLISH_CURSOR,
|
||||
PRESENTATION_SET_PAGE_INFINITE_CANVAS,
|
||||
};
|
||||
|
@ -89,6 +89,14 @@ const intlMessages = defineMessages({
|
||||
id: 'app.whiteboard.toolbar.multiUserOff',
|
||||
description: 'Whiteboard toolbar turn multi-user off menu',
|
||||
},
|
||||
infiniteCanvasOn: {
|
||||
id: 'app.whiteboard.toolbar.infiniteCanvasOn',
|
||||
description: 'Whiteboard toolbar turn infinite canvas on',
|
||||
},
|
||||
infiniteCanvasOff: {
|
||||
id: 'app.whiteboard.toolbar.infiniteCanvasOff',
|
||||
description: 'Whiteboard toolbar turn infinite canvas off',
|
||||
},
|
||||
pan: {
|
||||
id: 'app.whiteboard.toolbar.tools.hand',
|
||||
description: 'presentation toolbar pan label',
|
||||
@ -120,7 +128,9 @@ class PresentationToolbar extends PureComponent {
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { zoom, setIsPanning, fitToWidth, fitToWidthHandler, currentSlideNum } = this.props;
|
||||
const {
|
||||
zoom, setIsPanning, fitToWidth, fitToWidthHandler, currentSlideNum,
|
||||
} = this.props;
|
||||
const { wasFTWActive } = this.state;
|
||||
|
||||
if (zoom <= HUNDRED_PERCENT && zoom !== prevProps.zoom && !fitToWidth) setIsPanning();
|
||||
@ -129,7 +139,7 @@ class PresentationToolbar extends PureComponent {
|
||||
setTimeout(() => {
|
||||
fitToWidthHandler();
|
||||
this.setWasActive(false);
|
||||
}, 150)
|
||||
}, 150);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,10 +147,6 @@ class PresentationToolbar extends PureComponent {
|
||||
document.removeEventListener('keydown', this.switchSlide);
|
||||
}
|
||||
|
||||
setWasActive(wasFTWActive) {
|
||||
this.setState({ wasFTWActive });
|
||||
}
|
||||
|
||||
handleFTWSlideChange() {
|
||||
const { fitToWidth, fitToWidthHandler } = this.props;
|
||||
if (fitToWidth) {
|
||||
@ -171,6 +177,10 @@ class PresentationToolbar extends PureComponent {
|
||||
return addWhiteboardGlobalAccess(whiteboardId);
|
||||
}
|
||||
|
||||
setWasActive(wasFTWActive) {
|
||||
this.setState({ wasFTWActive });
|
||||
}
|
||||
|
||||
fullscreenToggleHandler() {
|
||||
const {
|
||||
fullscreenElementId,
|
||||
@ -343,6 +353,9 @@ class PresentationToolbar extends PureComponent {
|
||||
slidePosition,
|
||||
multiUserSize,
|
||||
multiUser,
|
||||
setPresentationPageInfiniteCanvas,
|
||||
allowInfiniteCanvas,
|
||||
infiniteCanvasIcon,
|
||||
} = this.props;
|
||||
|
||||
const { isMobile } = deviceInfo;
|
||||
@ -360,6 +373,8 @@ class PresentationToolbar extends PureComponent {
|
||||
: `${intl.formatMessage(intlMessages.nextSlideLabel)} (${currentSlideNum >= 1 ? currentSlideNum + 1 : ''
|
||||
})`;
|
||||
|
||||
const isInfiniteCanvas = currentSlide?.infiniteCanvas;
|
||||
|
||||
return (
|
||||
<Styled.PresentationToolbarWrapper
|
||||
id="presentationToolbarWrapper"
|
||||
@ -433,6 +448,30 @@ class PresentationToolbar extends PureComponent {
|
||||
/>
|
||||
</Styled.PresentationSlideControls>
|
||||
<Styled.PresentationZoomControls>
|
||||
{(allowInfiniteCanvas) && (
|
||||
<Styled.InfiniteCanvasButton
|
||||
data-test={isInfiniteCanvas ? 'turnInfiniteCanvasOff' : 'turnInfiniteCanvasOn'}
|
||||
role="button"
|
||||
aria-label={
|
||||
isInfiniteCanvas
|
||||
? intl.formatMessage(intlMessages.infiniteCanvasOff)
|
||||
: intl.formatMessage(intlMessages.infiniteCanvasOn)
|
||||
}
|
||||
color="light"
|
||||
disabled={!isMeteorConnected}
|
||||
customIcon={infiniteCanvasIcon(isInfiniteCanvas)}
|
||||
size="md"
|
||||
circle
|
||||
onClick={() => { setPresentationPageInfiniteCanvas(!isInfiniteCanvas); }}
|
||||
label={
|
||||
isInfiniteCanvas
|
||||
? intl.formatMessage(intlMessages.infiniteCanvasOff)
|
||||
: intl.formatMessage(intlMessages.infiniteCanvasOn)
|
||||
}
|
||||
hideLabel
|
||||
/>
|
||||
)}
|
||||
|
||||
<Styled.WBAccessButton
|
||||
data-test={multiUser ? 'turnMultiUsersWhiteboardOff' : 'turnMultiUsersWhiteboardOn'}
|
||||
role="button"
|
||||
@ -473,6 +512,7 @@ class PresentationToolbar extends PureComponent {
|
||||
minBound={HUNDRED_PERCENT}
|
||||
maxBound={MAX_PERCENT}
|
||||
step={STEP}
|
||||
isInfiniteCanvas={isInfiniteCanvas}
|
||||
isMeteorConnected={isMeteorConnected}
|
||||
/>
|
||||
</TooltipContainer>
|
||||
|
@ -5,14 +5,85 @@ import FullscreenService from '/imports/ui/components/common/fullscreen-button/s
|
||||
import { useIsPollingEnabled } from '/imports/ui/services/features';
|
||||
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
|
||||
import { POLL_CANCEL, POLL_CREATE } from '/imports/ui/components/poll/mutations';
|
||||
import { PRESENTATION_SET_PAGE } from '../mutations';
|
||||
import { PRESENTATION_SET_PAGE, PRESENTATION_SET_PAGE_INFINITE_CANVAS } from '../mutations';
|
||||
import PresentationToolbar from './component';
|
||||
import Session from '/imports/ui/services/storage/in-memory';
|
||||
|
||||
const infiniteCanvasIcon = (isInfiniteCanvas) => {
|
||||
if (isInfiniteCanvas) {
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.6667 3H1.33333C1.14924 3 1 3.14924 1 3.33333V13.3333C1 13.5174
|
||||
1.14924 13.6667 1.33333 13.6667H14.6667C14.8508 13.6667 15 13.5174 15 13.3333V3.33333C15
|
||||
3.14924 14.8508 3 14.6667 3ZM1.33333 2C0.596954 2 0 2.59695 0 3.33333V13.3333C0 14.0697
|
||||
0.596953 14.6667 1.33333 14.6667H14.6667C15.403 14.6667 16 14.0697 16 13.3333V3.33333C16
|
||||
2.59695 15.403 2 14.6667 2H1.33333Z"
|
||||
fill="#4E5A66"
|
||||
/>
|
||||
<path
|
||||
d="M12.875 11.875L9.125 8.125M9.125 8.125L9.125 10.9375M9.125 8.125L11.9375 8.125"
|
||||
stroke="#4E5A66"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M3.125 5.125L6.875 8.875M6.875 8.875L6.875 6.0625M6.875 8.875L4.0625 8.875"
|
||||
stroke="#4E5A66"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<svg
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M14.6667 3H1.33333C1.14924 3 1 3.14924 1 3.33333V13.3333C1 13.5174
|
||||
1.14924 13.6667 1.33333 13.6667H14.6667C14.8508 13.6667 15 13.5174 15 13.3333V3.33333C15
|
||||
3.14924 14.8508 3 14.6667 3ZM1.33333 2C0.596954 2 0 2.59695 0 3.33333V13.3333C0 14.0697
|
||||
0.596953 14.6667 1.33333 14.6667H14.6667C15.403 14.6667 16 14.0697 16 13.3333V3.33333C16
|
||||
2.59695 15.403 2 14.6667 2H1.33333Z"
|
||||
fill="#4E5A66"
|
||||
/>
|
||||
<path
|
||||
d="M9.125 8.125L12.875 11.875M12.875 11.875L12.875 9.0625M12.875 11.875L10.0625 11.875"
|
||||
stroke="#4E5A66"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M6.875 8.875L3.125 5.125M3.125 5.125L3.125 7.9375M3.125 5.125L5.9375 5.125"
|
||||
stroke="#4E5A66"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
const PresentationToolbarContainer = (props) => {
|
||||
const pluginsContext = useContext(PluginsContext);
|
||||
const { pluginsExtensibleAreasAggregatedState } = pluginsContext;
|
||||
|
||||
const WHITEBOARD_CONFIG = window.meetingClientSettings.public.whiteboard;
|
||||
|
||||
const {
|
||||
userIsPresenter,
|
||||
layoutSwapped,
|
||||
@ -20,6 +91,8 @@ const PresentationToolbarContainer = (props) => {
|
||||
presentationId,
|
||||
numberOfSlides,
|
||||
hasPoll,
|
||||
currentSlide,
|
||||
currentPresentationPage,
|
||||
} = props;
|
||||
|
||||
const handleToggleFullScreen = (ref) => FullscreenService.toggleFullScreen(ref);
|
||||
@ -27,6 +100,7 @@ const PresentationToolbarContainer = (props) => {
|
||||
const [stopPoll] = useMutation(POLL_CANCEL);
|
||||
const [createPoll] = useMutation(POLL_CREATE);
|
||||
const [presentationSetPage] = useMutation(PRESENTATION_SET_PAGE);
|
||||
const [presentationSetPageInfiniteCanvas] = useMutation(PRESENTATION_SET_PAGE_INFINITE_CANVAS);
|
||||
|
||||
const endCurrentPoll = () => {
|
||||
if (hasPoll) stopPoll();
|
||||
@ -41,6 +115,16 @@ const PresentationToolbarContainer = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const setPresentationPageInfiniteCanvas = (infiniteCanvas) => {
|
||||
const pageId = `${presentationId}/${currentSlideNum}`;
|
||||
presentationSetPageInfiniteCanvas({
|
||||
variables: {
|
||||
pageId,
|
||||
infiniteCanvas,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const skipToSlide = (slideNum) => {
|
||||
const slideId = `${presentationId}/${slideNum}`;
|
||||
setPresentationPage(slideId);
|
||||
@ -93,6 +177,7 @@ const PresentationToolbarContainer = (props) => {
|
||||
amIPresenter={userIsPresenter}
|
||||
endCurrentPoll={endCurrentPoll}
|
||||
isPollingEnabled={isPollingEnabled}
|
||||
allowInfiniteCanvas={WHITEBOARD_CONFIG?.allowInfiniteCanvas}
|
||||
// TODO: Remove this
|
||||
isMeteorConnected
|
||||
{...{
|
||||
@ -102,6 +187,10 @@ const PresentationToolbarContainer = (props) => {
|
||||
previousSlide,
|
||||
nextSlide,
|
||||
skipToSlide,
|
||||
setPresentationPageInfiniteCanvas,
|
||||
currentSlide,
|
||||
currentPresentationPage,
|
||||
infiniteCanvasIcon,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
@ -281,6 +281,34 @@ const WBAccessButton = styled(Button)`
|
||||
}
|
||||
`;
|
||||
|
||||
const InfiniteCanvasButton = styled(Button)`
|
||||
border: none !important;
|
||||
|
||||
svg {
|
||||
[dir="rtl"] & {
|
||||
-webkit-transform: scale(-1, 1);
|
||||
-moz-transform: scale(-1, 1);
|
||||
-ms-transform: scale(-1, 1);
|
||||
-o-transform: scale(-1, 1);
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
position: relative;
|
||||
color: ${toolbarButtonColor};
|
||||
background-color: ${colorOffWhite};
|
||||
border-radius: 0;
|
||||
box-shadow: none !important;
|
||||
border: 0;
|
||||
margin-left: 2px;
|
||||
margin-right: 2px;
|
||||
|
||||
&:focus {
|
||||
background-color: ${colorOffWhite};
|
||||
border: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
PresentationToolbarWrapper,
|
||||
QuickPollButton,
|
||||
@ -294,4 +322,5 @@ export default {
|
||||
MultiUserTool,
|
||||
WBAccessButton,
|
||||
MUTPlaceholder,
|
||||
InfiniteCanvasButton,
|
||||
};
|
||||
|
@ -147,6 +147,7 @@ class ZoomTool extends PureComponent {
|
||||
intl,
|
||||
isMeteorConnected,
|
||||
step,
|
||||
isInfiniteCanvas,
|
||||
} = this.props;
|
||||
const { stateZoomValue } = this.state;
|
||||
|
||||
@ -170,6 +171,7 @@ class ZoomTool extends PureComponent {
|
||||
exec={this.decrement}
|
||||
value={zoomValue}
|
||||
minBound={minBound}
|
||||
isInfiniteCanvas={isInfiniteCanvas}
|
||||
>
|
||||
<Styled.DecreaseZoomButton
|
||||
color="light"
|
||||
@ -182,7 +184,7 @@ class ZoomTool extends PureComponent {
|
||||
data-test="zoomOutBtn"
|
||||
icon="substract"
|
||||
onClick={() => { }}
|
||||
disabled={(zoomValue <= minBound) || !isMeteorConnected}
|
||||
disabled={(zoomValue <= minBound) || !isMeteorConnected || isInfiniteCanvas}
|
||||
hideLabel
|
||||
/>
|
||||
<div id="zoomOutDescription" hidden>{intl.formatMessage(intlMessages.zoomOutDesc)}</div>
|
||||
@ -213,6 +215,7 @@ class ZoomTool extends PureComponent {
|
||||
exec={this.increment}
|
||||
value={zoomValue}
|
||||
maxBound={maxBound}
|
||||
isInfiniteCanvas={isInfiniteCanvas}
|
||||
>
|
||||
<Styled.IncreaseZoomButton
|
||||
color="light"
|
||||
@ -225,7 +228,7 @@ class ZoomTool extends PureComponent {
|
||||
data-test="zoomInBtn"
|
||||
icon="add"
|
||||
onClick={() => { }}
|
||||
disabled={(zoomValue >= maxBound) || !isMeteorConnected}
|
||||
disabled={(zoomValue >= maxBound) || !isMeteorConnected || isInfiniteCanvas}
|
||||
hideLabel
|
||||
/>
|
||||
<div id="zoomInDescription" hidden>{intl.formatMessage(intlMessages.zoomInDesc)}</div>
|
||||
|
@ -26,9 +26,10 @@ class HoldDownButton extends PureComponent {
|
||||
minBound,
|
||||
maxBound,
|
||||
value,
|
||||
isInfiniteCanvas
|
||||
} = this.props;
|
||||
const bounds = (value === maxBound) || (value === minBound);
|
||||
if (bounds) return;
|
||||
if (bounds || isInfiniteCanvas) return;
|
||||
exec();
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,7 @@ const Whiteboard = React.memo(function Whiteboard(props) {
|
||||
locale,
|
||||
darkTheme,
|
||||
selectedLayout,
|
||||
isInfiniteCanvas,
|
||||
} = props;
|
||||
|
||||
clearTldrawCache();
|
||||
@ -648,7 +649,7 @@ const Whiteboard = React.memo(function Whiteboard(props) {
|
||||
|
||||
// Adjust camera position to ensure it stays within bounds
|
||||
const panned = next?.id?.includes("camera") && (prev.x !== next.x || prev.y !== next.y);
|
||||
if (panned) {
|
||||
if (panned && !currentPresentationPageRef.current?.infiniteCanvas) {
|
||||
// Horizontal bounds check
|
||||
if (next.x > 0) {
|
||||
next.x = 0;
|
||||
|
@ -53,6 +53,7 @@ export const CURRENT_PRESENTATION_PAGE_SUBSCRIPTION = gql`subscription CurrentPr
|
||||
downloadable
|
||||
presentationName
|
||||
isDefaultPresentation
|
||||
infiniteCanvas
|
||||
}
|
||||
}`;
|
||||
|
||||
|
@ -51,7 +51,6 @@ const TldrawV2GlobalStyle = createGlobalStyle`
|
||||
position: relative;
|
||||
}
|
||||
|
||||
// Add the following lines to override height and width attributes for .tl-overlays__item
|
||||
.tl-overlays__item {
|
||||
height: auto !important;
|
||||
width: auto !important;
|
||||
@ -103,6 +102,7 @@ const TldrawV2GlobalStyle = createGlobalStyle`
|
||||
}
|
||||
`}
|
||||
|
||||
.tlui-helper-buttons,
|
||||
[data-testid="main.page-menu"],
|
||||
[data-testid="main.menu"],
|
||||
[data-testid="tools.more.laser"],
|
||||
|
@ -810,6 +810,7 @@ export const meetingClientSettingsInitialValues: MeetingClientSettings = {
|
||||
pointerDiameter: 5,
|
||||
maxStickyNoteLength: 1000,
|
||||
maxNumberOfAnnotations: 300,
|
||||
allowInfiniteCanvas: true,
|
||||
annotations: {
|
||||
status: {
|
||||
start: 'DRAW_START',
|
||||
|
@ -997,6 +997,7 @@ public:
|
||||
whiteboard:
|
||||
annotationsQueueProcessInterval: 60
|
||||
cursorInterval: 150
|
||||
allowInfiniteCanvas: true
|
||||
pointerDiameter: 5
|
||||
maxStickyNoteLength: 1000
|
||||
# limit number of annotations per slide
|
||||
|
@ -1180,6 +1180,8 @@
|
||||
"app.whiteboard.toolbar.clearConfirmation": "Are you sure you want to clear all annotations?",
|
||||
"app.whiteboard.toolbar.multiUserOn": "Turn multi-user whiteboard on",
|
||||
"app.whiteboard.toolbar.multiUserOff": "Turn multi-user whiteboard off",
|
||||
"app.whiteboard.toolbar.infiniteCanvasOn": "Turn infinite canvas on",
|
||||
"app.whiteboard.toolbar.infiniteCanvasOff": "Turn infinite canvas off",
|
||||
"app.whiteboard.toolbar.palmRejectionOn": "Turn palm rejection on",
|
||||
"app.whiteboard.toolbar.palmRejectionOff": "Turn palm rejection off",
|
||||
"app.whiteboard.toolbar.fontSize": "Font size list",
|
||||
|
Loading…
Reference in New Issue
Block a user