Merge pull request #19479 from ramonlsouza/migrate-presen-actions
refactor: migrate presentation actions
This commit is contained in:
commit
2c5a7f3f00
27
bbb-graphql-actions/src/actions/presentationExport.ts
Normal file
27
bbb-graphql-actions/src/actions/presentationExport.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { RedisMessage } from '../types';
|
||||
import {throwErrorIfNotPresenter} from "../imports/validation";
|
||||
|
||||
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
|
||||
throwErrorIfNotPresenter(sessionVariables);
|
||||
const eventName = `MakePresentationDownloadReqMsg`;
|
||||
|
||||
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 = {
|
||||
presId: input.presentationId,
|
||||
allPages: true,
|
||||
fileStateType: input.fileStateType,
|
||||
pages: [],
|
||||
};
|
||||
|
||||
return { eventName, routing, header, body };
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import { RedisMessage } from '../types';
|
||||
import { ValidationError } from '../types/ValidationError';
|
||||
import {throwErrorIfNotPresenter} from "../imports/validation";
|
||||
|
||||
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
|
||||
|
@ -280,6 +280,13 @@ type Mutation {
|
||||
): Boolean
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
presentationExport(
|
||||
presentationId: String!
|
||||
fileStateType: String!
|
||||
): Boolean
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
presentationRemove(
|
||||
presentationId: String!
|
||||
|
@ -245,6 +245,13 @@ actions:
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
- name: presentationExport
|
||||
definition:
|
||||
kind: synchronous
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
comment: presentationExport
|
||||
- name: presentationRemove
|
||||
definition:
|
||||
kind: synchronous
|
||||
|
@ -1 +0,0 @@
|
||||
import './methods';
|
@ -1,12 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import clearWhiteboard from './methods/clearWhiteboard';
|
||||
import sendAnnotations from './methods/sendAnnotations';
|
||||
import sendBulkAnnotations from './methods/sendBulkAnnotations';
|
||||
import deleteAnnotations from './methods/deleteAnnotations';
|
||||
|
||||
Meteor.methods({
|
||||
clearWhiteboard,
|
||||
sendAnnotations,
|
||||
sendBulkAnnotations,
|
||||
deleteAnnotations,
|
||||
});
|
@ -1,27 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function clearWhiteboard(whiteboardId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'ClearWhiteboardPubMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(whiteboardId, String);
|
||||
|
||||
const payload = {
|
||||
whiteboardId,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method clearWhiteboard ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function deleteAnnotations(annotations, whiteboardId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'DeleteWhiteboardAnnotationsPubMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(whiteboardId, String);
|
||||
check(annotations, Array);
|
||||
|
||||
const payload = {
|
||||
whiteboardId,
|
||||
annotationsIds: annotations,
|
||||
};
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method deleteAnnotation ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function sendAnnotationHelper(annotations, meetingId, requesterUserId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SendWhiteboardAnnotationsPubMsg';
|
||||
|
||||
try {
|
||||
check(annotations, Array);
|
||||
// TODO see if really necessary, don't know if it's possible
|
||||
// to have annotations from different pages
|
||||
// group annotations by same whiteboardId
|
||||
const groupedAnnotations = annotations.reduce((r, v, i, a, k = v.wbId) => ((r[k] || (r[k] = [])).push(v), r), {}) //groupBy wbId
|
||||
|
||||
Object.entries(groupedAnnotations).forEach(([_, whiteboardAnnotations]) => {
|
||||
const whiteboardId = whiteboardAnnotations[0].wbId;
|
||||
check(whiteboardId, String);
|
||||
|
||||
const payload = {
|
||||
whiteboardId,
|
||||
annotations: whiteboardAnnotations,
|
||||
html5InstanceId: parseInt(process.env.INSTANCE_ID, 10) || 1,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method sendAnnotationHelper ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
import { check } from 'meteor/check';
|
||||
import sendAnnotationHelper from './sendAnnotationHelper';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function sendAnnotations(annotations) {
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
sendAnnotationHelper(annotations, meetingId, requesterUserId);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method sendAnnotation ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import sendAnnotationHelper from './sendAnnotationHelper';
|
||||
import { check } from 'meteor/check';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function sendBulkAnnotations(payload) {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
try {
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
|
||||
console.log("!!!!!!! sendBulkAnnotations!!!!:", payload)
|
||||
|
||||
sendAnnotationHelper(payload, meetingId, requesterUserId);
|
||||
//payload.forEach((annotation) => sendAnnotationHelper(annotation, meetingId, requesterUserId));
|
||||
return true;
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method sendBulkAnnotations ${err.stack}`);
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
import './methods';
|
@ -1,12 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import removePresentation from './methods/removePresentation';
|
||||
import setPresentation from './methods/setPresentation';
|
||||
import setPresentationDownloadable from './methods/setPresentationDownloadable';
|
||||
import exportPresentation from './methods/exportPresentation';
|
||||
|
||||
Meteor.methods({
|
||||
removePresentation,
|
||||
setPresentation,
|
||||
setPresentationDownloadable,
|
||||
exportPresentation,
|
||||
});
|
@ -1,29 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default async function exportPresentation(presentationId, fileStateType) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'MakePresentationDownloadReqMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(presentationId, String);
|
||||
|
||||
const payload = {
|
||||
presId: presentationId,
|
||||
allPages: true,
|
||||
fileStateType,
|
||||
pages: [],
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method exportPresentation ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function removePresentation(presentationId, podId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'RemovePresentationPubMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(presentationId, String);
|
||||
check(podId, String);
|
||||
|
||||
const payload = {
|
||||
presentationId,
|
||||
podId,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method removePresentation ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function setPresentation(presentationId, podId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SetCurrentPresentationPubMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(presentationId, String);
|
||||
check(podId, String);
|
||||
|
||||
const payload = {
|
||||
presentationId,
|
||||
podId,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method setPresentation ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { check } from 'meteor/check';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default function setPresentationDownloadable(presentationId, downloadable, fileStateType) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SetPresentationDownloadablePubMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(downloadable, Match.Maybe(Boolean));
|
||||
check(presentationId, String);
|
||||
check(fileStateType, Match.Maybe(String));
|
||||
|
||||
const payload = {
|
||||
presentationId,
|
||||
podId: 'DEFAULT_PRESENTATION_POD',
|
||||
downloadable,
|
||||
fileStateType,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method setPresentationDownloadable ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
import './methods';
|
@ -1,6 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import switchSlide from './methods/switchSlide';
|
||||
|
||||
Meteor.methods({
|
||||
switchSlide,
|
||||
});
|
@ -1,30 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { check } from 'meteor/check';
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { extractCredentials } from '/imports/api/common/server/helpers';
|
||||
import Logger from '/imports/startup/server/logger';
|
||||
|
||||
export default async function switchSlide(slideNumber, podId, presentationId) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SetCurrentPagePubMsg';
|
||||
|
||||
try {
|
||||
const { meetingId, requesterUserId } = extractCredentials(this.userId);
|
||||
|
||||
check(meetingId, String);
|
||||
check(requesterUserId, String);
|
||||
check(slideNumber, Number);
|
||||
check(podId, String);
|
||||
|
||||
const payload = {
|
||||
podId,
|
||||
presentationId,
|
||||
pageId: `${presentationId}/${slideNumber}`,
|
||||
};
|
||||
|
||||
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
} catch (err) {
|
||||
Logger.error(`Exception while invoking method switchSlide ${err.stack}`);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import PresentationUploaderService from '/imports/ui/components/presentation/presentation-uploader/service';
|
||||
import ActionsDropdown from './component';
|
||||
import { layoutSelectInput, layoutDispatch, layoutSelect } from '../../layout/context';
|
||||
import { SMALL_VIEWPORT_BREAKPOINT, ACTIONS, PANELS } from '../../layout/enums';
|
||||
@ -12,6 +11,7 @@ import {
|
||||
import { SET_PRESENTER } from '/imports/ui/core/graphql/mutations/userMutations';
|
||||
import { TIMER_ACTIVATE, TIMER_DEACTIVATE } from '../../timer/mutations';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { PRESENTATION_SET_CURRENT } from '../../presentation/mutations';
|
||||
|
||||
const TIMER_CONFIG = Meteor.settings.public.timer;
|
||||
const MILLI_IN_MINUTE = 60000;
|
||||
@ -35,11 +35,16 @@ const ActionsDropdownContainer = (props) => {
|
||||
const [setPresenter] = useMutation(SET_PRESENTER);
|
||||
const [timerActivate] = useMutation(TIMER_ACTIVATE);
|
||||
const [timerDeactivate] = useMutation(TIMER_DEACTIVATE);
|
||||
const [presentationSetCurrent] = useMutation(PRESENTATION_SET_CURRENT);
|
||||
|
||||
const handleTakePresenter = () => {
|
||||
setPresenter({ variables: { userId: Auth.userID } });
|
||||
};
|
||||
|
||||
const setPresentation = (presentationId) => {
|
||||
presentationSetCurrent({ variables: { presentationId } });
|
||||
};
|
||||
|
||||
const activateTimer = () => {
|
||||
const stopwatch = true;
|
||||
const running = false;
|
||||
@ -71,7 +76,7 @@ const ActionsDropdownContainer = (props) => {
|
||||
presentations,
|
||||
isTimerFeatureEnabled: isTimerFeatureEnabled(),
|
||||
isDropdownOpen: Session.get('dropdownOpen'),
|
||||
setPresentation: PresentationUploaderService.setPresentation,
|
||||
setPresentation,
|
||||
isCameraAsContentEnabled: isCameraAsContentEnabled(),
|
||||
handleTakePresenter,
|
||||
activateTimer,
|
||||
|
@ -51,6 +51,8 @@ class NotesDropdown extends PureComponent {
|
||||
intl,
|
||||
amIPresenter,
|
||||
presentations,
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
stopExternalVideoShare,
|
||||
} = this.props;
|
||||
|
||||
@ -72,7 +74,7 @@ class NotesDropdown extends PureComponent {
|
||||
onClick: () => {
|
||||
this.setConverterButtonDisabled(true);
|
||||
setTimeout(() => this.setConverterButtonDisabled(false), DEBOUNCE_TIMEOUT);
|
||||
return Service.convertAndUpload(presentations);
|
||||
return Service.convertAndUpload(presentations, setPresentation, removePresentation);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
PROCESSED_PRESENTATIONS_SUBSCRIPTION,
|
||||
} from '/imports/ui/components/whiteboard/queries';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import { PRESENTATION_SET_CURRENT, PRESENTATION_REMOVE } from '../../presentation/mutations';
|
||||
import { EXTERNAL_VIDEO_STOP } from '../../external-video-player/mutations';
|
||||
|
||||
const NotesDropdownContainer = ({ ...props }) => {
|
||||
@ -18,8 +19,18 @@ const NotesDropdownContainer = ({ ...props }) => {
|
||||
const { data: presentationData } = useSubscription(PROCESSED_PRESENTATIONS_SUBSCRIPTION);
|
||||
const presentations = presentationData?.pres_presentation || [];
|
||||
|
||||
const [presentationSetCurrent] = useMutation(PRESENTATION_SET_CURRENT);
|
||||
const [presentationRemove] = useMutation(PRESENTATION_REMOVE);
|
||||
const [stopExternalVideoShare] = useMutation(EXTERNAL_VIDEO_STOP);
|
||||
|
||||
const setPresentation = (presentationId) => {
|
||||
presentationSetCurrent({ variables: { presentationId } });
|
||||
};
|
||||
|
||||
const removePresentation = (presentationId) => {
|
||||
presentationRemove({ variables: { presentationId } });
|
||||
};
|
||||
|
||||
return (
|
||||
<NotesDropdown
|
||||
{
|
||||
@ -27,6 +38,8 @@ const NotesDropdownContainer = ({ ...props }) => {
|
||||
amIPresenter,
|
||||
isRTL,
|
||||
presentations,
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
stopExternalVideoShare,
|
||||
...props,
|
||||
}
|
||||
|
@ -7,8 +7,7 @@ import { uniqueId } from '/imports/utils/string-utils';
|
||||
|
||||
const PADS_CONFIG = Meteor.settings.public.pads;
|
||||
|
||||
async function convertAndUpload(presentations) {
|
||||
|
||||
async function convertAndUpload(presentations, setPresentation, removePresentation) {
|
||||
let filename = 'Shared_Notes';
|
||||
const duplicates = presentations.filter((pres) => pres.filename?.startsWith(filename) || pres.name?.startsWith(filename)).length;
|
||||
|
||||
@ -53,7 +52,9 @@ async function convertAndUpload(presentations) {
|
||||
onUpload: () => { },
|
||||
onProgress: () => { },
|
||||
onDone: () => { },
|
||||
});
|
||||
},
|
||||
setPresentation,
|
||||
removePresentation);
|
||||
}
|
||||
|
||||
export default {
|
||||
|
@ -38,7 +38,7 @@ const ButtonWrapper = styled.div`
|
||||
background-color: ${colorTransparent};
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
z-index: 2;
|
||||
z-index: 999;
|
||||
margin: 2px;
|
||||
bottom: 0;
|
||||
|
||||
|
@ -23,7 +23,81 @@ export const PRESENTATION_SET_WRITERS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_SET_PAGE = gql`
|
||||
mutation PresentationSetPage($presentationId: String!, $pageId: String!) {
|
||||
presentationSetPage(
|
||||
presentationId: $presentationId,
|
||||
pageId: $pageId,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_SET_DOWNLOADABLE = gql`
|
||||
mutation PresentationSetDownloadable(
|
||||
$presentationId: String!,
|
||||
$downloadable: Boolean!,
|
||||
$fileStateType: String!,) {
|
||||
presentationSetDownloadable(
|
||||
presentationId: $presentationId,
|
||||
downloadable: $downloadable,
|
||||
fileStateType: $fileStateType,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_EXPORT = gql`
|
||||
mutation PresentationExport(
|
||||
$presentationId: String!,
|
||||
$fileStateType: String!,) {
|
||||
presentationExport(
|
||||
presentationId: $presentationId,
|
||||
fileStateType: $fileStateType,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_SET_CURRENT = gql`
|
||||
mutation PresentationSetCurrent($presentationId: String!) {
|
||||
presentationSetCurrent(
|
||||
presentationId: $presentationId,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_REMOVE = gql`
|
||||
mutation PresentationRemove($presentationId: String!) {
|
||||
presentationRemove(
|
||||
presentationId: $presentationId,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRES_ANNOTATION_DELETE = gql`
|
||||
mutation PresAnnotationDelete($pageId: String!, $annotationsIds: [String]!) {
|
||||
presAnnotationDelete(
|
||||
pageId: $pageId,
|
||||
annotationsIds: $annotationsIds,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRES_ANNOTATION_SUBMIT = gql`
|
||||
mutation PresAnnotationSubmit($pageId: String!, $annotations: json!) {
|
||||
presAnnotationSubmit(
|
||||
pageId: $pageId,
|
||||
annotations: $annotations,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
PRESENTATION_SET_ZOOM,
|
||||
PRESENTATION_SET_WRITERS,
|
||||
PRESENTATION_SET_PAGE,
|
||||
PRESENTATION_SET_DOWNLOADABLE,
|
||||
PRESENTATION_EXPORT,
|
||||
PRESENTATION_SET_CURRENT,
|
||||
PRESENTATION_REMOVE,
|
||||
PRES_ANNOTATION_DELETE,
|
||||
PRES_ANNOTATION_SUBMIT,
|
||||
};
|
||||
|
@ -150,12 +150,12 @@ class PresentationToolbar extends PureComponent {
|
||||
}
|
||||
|
||||
handleSkipToSlideChange(event) {
|
||||
const { skipToSlide, presentationId } = this.props;
|
||||
const { skipToSlide } = this.props;
|
||||
const requestedSlideNum = Number.parseInt(event.target.value, 10);
|
||||
|
||||
this.handleFTWSlideChange();
|
||||
if (event) event.currentTarget.blur();
|
||||
skipToSlide(requestedSlideNum, presentationId);
|
||||
skipToSlide(requestedSlideNum);
|
||||
}
|
||||
|
||||
handleSwitchWhiteboardMode() {
|
||||
@ -194,29 +194,21 @@ class PresentationToolbar extends PureComponent {
|
||||
}
|
||||
|
||||
nextSlideHandler(event) {
|
||||
const {
|
||||
nextSlide,
|
||||
currentSlideNum,
|
||||
numberOfSlides,
|
||||
endCurrentPoll,
|
||||
presentationId,
|
||||
} = this.props;
|
||||
const { nextSlide, endCurrentPoll } = this.props;
|
||||
|
||||
this.handleFTWSlideChange();
|
||||
if (event) event.currentTarget.blur();
|
||||
endCurrentPoll();
|
||||
nextSlide(currentSlideNum, numberOfSlides, presentationId);
|
||||
nextSlide();
|
||||
}
|
||||
|
||||
previousSlideHandler(event) {
|
||||
const {
|
||||
previousSlide, currentSlideNum, endCurrentPoll, presentationId
|
||||
} = this.props;
|
||||
const { previousSlide, endCurrentPoll } = this.props;
|
||||
|
||||
this.handleFTWSlideChange();
|
||||
if (event) event.currentTarget.blur();
|
||||
endCurrentPoll();
|
||||
previousSlide(currentSlideNum, presentationId);
|
||||
previousSlide();
|
||||
}
|
||||
|
||||
switchSlide(event) {
|
||||
|
@ -2,19 +2,24 @@ import React, { useContext } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import PresentationToolbar from './component';
|
||||
import PresentationToolbarService from './service';
|
||||
import FullscreenService from '/imports/ui/components/common/fullscreen-button/service';
|
||||
import { isPollingEnabled } from '/imports/ui/services/features';
|
||||
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
|
||||
import { useSubscription, useMutation } from '@apollo/client';
|
||||
import POLL_SUBSCRIPTION from '/imports/ui/core/graphql/queries/pollSubscription';
|
||||
import { POLL_CANCEL, POLL_CREATE } from '/imports/ui/components/poll/mutations';
|
||||
import { PRESENTATION_SET_PAGE } from '../mutations';
|
||||
|
||||
const PresentationToolbarContainer = (props) => {
|
||||
const pluginsContext = useContext(PluginsContext);
|
||||
const { pluginsExtensibleAreasAggregatedState } = pluginsContext;
|
||||
|
||||
const { userIsPresenter, layoutSwapped } = props;
|
||||
const {
|
||||
userIsPresenter,
|
||||
layoutSwapped,
|
||||
currentSlideNum,
|
||||
presentationId,
|
||||
} = props;
|
||||
|
||||
const { data: pollData } = useSubscription(POLL_SUBSCRIPTION);
|
||||
const hasPoll = pollData?.poll?.length > 0;
|
||||
@ -23,11 +28,36 @@ const PresentationToolbarContainer = (props) => {
|
||||
|
||||
const [stopPoll] = useMutation(POLL_CANCEL);
|
||||
const [createPoll] = useMutation(POLL_CREATE);
|
||||
const [presentationSetPage] = useMutation(PRESENTATION_SET_PAGE);
|
||||
|
||||
const endCurrentPoll = () => {
|
||||
if (hasPoll) stopPoll();
|
||||
};
|
||||
|
||||
const setPresentationPage = (pageId) => {
|
||||
presentationSetPage({
|
||||
variables: {
|
||||
presentationId,
|
||||
pageId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const skipToSlide = (slideNum) => {
|
||||
const slideId = `${presentationId}/${slideNum}`;
|
||||
setPresentationPage(slideId);
|
||||
};
|
||||
|
||||
const previousSlide = () => {
|
||||
const prevSlideNum = currentSlideNum - 1;
|
||||
skipToSlide(prevSlideNum);
|
||||
};
|
||||
|
||||
const nextSlide = () => {
|
||||
const nextSlideNum = currentSlideNum + 1;
|
||||
skipToSlide(nextSlideNum);
|
||||
};
|
||||
|
||||
const startPoll = (pollType, pollId, answers = [], question, isMultipleResponse = false) => {
|
||||
Session.set('openPanel', 'poll');
|
||||
Session.set('forcePollOpen', true);
|
||||
@ -60,6 +90,9 @@ const PresentationToolbarContainer = (props) => {
|
||||
pluginProvidedPresentationToolbarItems,
|
||||
handleToggleFullScreen,
|
||||
startPoll,
|
||||
previousSlide,
|
||||
nextSlide,
|
||||
skipToSlide,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@ -69,9 +102,6 @@ const PresentationToolbarContainer = (props) => {
|
||||
|
||||
export default withTracker(() => {
|
||||
return {
|
||||
nextSlide: PresentationToolbarService.nextSlide,
|
||||
previousSlide: PresentationToolbarService.previousSlide,
|
||||
skipToSlide: PresentationToolbarService.skipToSlide,
|
||||
isMeteorConnected: Meteor.status().connected,
|
||||
isPollingEnabled: isPollingEnabled(),
|
||||
};
|
||||
|
@ -1,25 +0,0 @@
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
|
||||
const POD_ID = 'DEFAULT_PRESENTATION_POD';
|
||||
|
||||
const previousSlide = (currentSlideNum, presentationId) => {
|
||||
if (currentSlideNum > 1) {
|
||||
makeCall('switchSlide', currentSlideNum - 1, POD_ID, presentationId);
|
||||
}
|
||||
};
|
||||
|
||||
const nextSlide = (currentSlideNum, numberOfSlides, presentationId) => {
|
||||
if (currentSlideNum < numberOfSlides) {
|
||||
makeCall('switchSlide', currentSlideNum + 1, POD_ID, presentationId);
|
||||
}
|
||||
};
|
||||
|
||||
const skipToSlide = (requestedSlideNum, presentationId) => {
|
||||
makeCall('switchSlide', requestedSlideNum, POD_ID, presentationId);
|
||||
};
|
||||
|
||||
export default {
|
||||
nextSlide,
|
||||
previousSlide,
|
||||
skipToSlide,
|
||||
};
|
@ -583,6 +583,8 @@ class PresentationUploader extends Component {
|
||||
selectedToBeNextCurrent,
|
||||
presentations: propPresentations,
|
||||
dispatchChangePresentationDownloadable,
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
} = this.props;
|
||||
const { disableActions, presentations } = this.state;
|
||||
const presentationsToSave = presentations;
|
||||
@ -610,7 +612,14 @@ class PresentationUploader extends Component {
|
||||
|
||||
if (!disableActions) {
|
||||
Session.set('showUploadPresentationView', false);
|
||||
return handleSave(presentationsToSave, true, {}, propPresentations)
|
||||
return handleSave(
|
||||
presentationsToSave,
|
||||
true,
|
||||
{},
|
||||
propPresentations,
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
)
|
||||
.then(() => {
|
||||
const hasError = presentations.some((p) => !!p.uploadErrorMsgKey);
|
||||
if (!hasError) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import { withTracker } from 'meteor/react-meteor-data';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import ErrorBoundary from '/imports/ui/components/common/error-boundary/component';
|
||||
import FallbackModal from '/imports/ui/components/common/fallback-errors/fallback-modal/component';
|
||||
import { useSubscription, useMutation } from '@apollo/client';
|
||||
import Service from './service';
|
||||
import PresUploaderToast from '/imports/ui/components/presentation/presentation-toast/presentation-uploader-toast/component';
|
||||
import PresentationUploader from './component';
|
||||
@ -13,11 +13,16 @@ import {
|
||||
isDownloadPresentationConvertedToPdfEnabled,
|
||||
isPresentationEnabled,
|
||||
} from '/imports/ui/services/features';
|
||||
import { useSubscription } from '@apollo/client';
|
||||
import {
|
||||
PRESENTATIONS_SUBSCRIPTION,
|
||||
} from '/imports/ui/components/whiteboard/queries';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import {
|
||||
PRESENTATION_SET_DOWNLOADABLE,
|
||||
PRESENTATION_EXPORT,
|
||||
PRESENTATION_SET_CURRENT,
|
||||
PRESENTATION_REMOVE,
|
||||
} from '../mutations';
|
||||
|
||||
const PRESENTATION_CONFIG = Meteor.settings.public.presentation;
|
||||
|
||||
@ -31,8 +36,36 @@ const PresentationUploaderContainer = (props) => {
|
||||
const presentations = presentationData?.pres_presentation || [];
|
||||
const currentPresentation = presentations.find((p) => p.current)?.presentationId || '';
|
||||
|
||||
const [presentationSetDownloadable] = useMutation(PRESENTATION_SET_DOWNLOADABLE);
|
||||
const [presentationExport] = useMutation(PRESENTATION_EXPORT);
|
||||
const [presentationSetCurrent] = useMutation(PRESENTATION_SET_CURRENT);
|
||||
const [presentationRemove] = useMutation(PRESENTATION_REMOVE);
|
||||
|
||||
const exportPresentation = (presentationId, fileStateType) => {
|
||||
makeCall('exportPresentation', presentationId, fileStateType);
|
||||
presentationExport({
|
||||
variables: {
|
||||
presentationId,
|
||||
fileStateType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const dispatchChangePresentationDownloadable = (presentationId, downloadable, fileStateType) => {
|
||||
presentationSetDownloadable({
|
||||
variables: {
|
||||
presentationId,
|
||||
downloadable,
|
||||
fileStateType,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const setPresentation = (presentationId) => {
|
||||
presentationSetCurrent({ variables: { presentationId } });
|
||||
};
|
||||
|
||||
const removePresentation = (presentationId) => {
|
||||
presentationRemove({ variables: { presentationId } });
|
||||
};
|
||||
|
||||
return userIsPresenter && (
|
||||
@ -42,6 +75,9 @@ const PresentationUploaderContainer = (props) => {
|
||||
presentations={presentations}
|
||||
currentPresentation={currentPresentation}
|
||||
exportPresentation={exportPresentation}
|
||||
dispatchChangePresentationDownloadable={dispatchChangePresentationDownloadable}
|
||||
setPresentation={setPresentation}
|
||||
removePresentation={removePresentation}
|
||||
{...props}
|
||||
/>
|
||||
</ErrorBoundary>
|
||||
@ -52,7 +88,6 @@ export default withTracker(() => {
|
||||
const {
|
||||
dispatchDisableDownloadable,
|
||||
dispatchEnableDownloadable,
|
||||
dispatchChangePresentationDownloadable,
|
||||
} = Service;
|
||||
const isOpen = isPresentationEnabled() && (Session.get('showUploadPresentationView') || false);
|
||||
|
||||
@ -70,7 +105,6 @@ export default withTracker(() => {
|
||||
renderPresentationItemStatus: PresUploaderToast.renderPresentationItemStatus,
|
||||
dispatchDisableDownloadable,
|
||||
dispatchEnableDownloadable,
|
||||
dispatchChangePresentationDownloadable,
|
||||
isOpen,
|
||||
selectedToBeNextCurrent: Session.get('selectedToBeNextCurrent') || null,
|
||||
externalUploadData: Service.getExternalUploadData(),
|
||||
|
@ -103,7 +103,7 @@ class PresentationDownloadDropdown extends PureComponent {
|
||||
const downloadableExtension = downloadFileUri?.split('.').slice(-1)[0];
|
||||
const originalFileExtension = name?.split('.').slice(-1)[0];
|
||||
const changeDownloadOriginalOrConvertedPresentation = (enableDownload, fileStateType) => {
|
||||
handleDownloadableChange(item, fileStateType, enableDownload);
|
||||
handleDownloadableChange(item?.presentationId, fileStateType, enableDownload);
|
||||
if (enableDownload) {
|
||||
handleDownloadingOfPresentation(fileStateType);
|
||||
}
|
||||
|
@ -41,10 +41,6 @@ const futch = (url, opts = {}, onProgress) => new Promise((res, rej) => {
|
||||
xhr.send(opts.body);
|
||||
});
|
||||
|
||||
const dispatchChangePresentationDownloadable = (presentation, newState, fileStateType) => {
|
||||
makeCall('setPresentationDownloadable', presentation.presentationId, newState, fileStateType);
|
||||
};
|
||||
|
||||
const requestPresentationUploadToken = (
|
||||
temporaryPresentationId,
|
||||
meetingId,
|
||||
@ -183,19 +179,18 @@ const uploadAndConvertPresentations = (
|
||||
p.onUpload, p.onProgress, p.onConversion, p.current,
|
||||
)));
|
||||
|
||||
const setPresentation = (presentationId) => {
|
||||
makeCall('setPresentation', presentationId, POD_ID);
|
||||
};
|
||||
|
||||
const removePresentation = (presentationId) => {
|
||||
makeCall('removePresentation', presentationId, POD_ID);
|
||||
};
|
||||
|
||||
const removePresentations = (
|
||||
presentationsToRemove,
|
||||
) => Promise.all(presentationsToRemove.map((p) => removePresentation(p.presentationId, POD_ID)));
|
||||
removePresentation,
|
||||
) => Promise.all(presentationsToRemove.map((p) => removePresentation(p.presentationId)));
|
||||
|
||||
const persistPresentationChanges = (oldState, newState, uploadEndpoint) => {
|
||||
const persistPresentationChanges = (
|
||||
oldState,
|
||||
newState,
|
||||
uploadEndpoint,
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
) => {
|
||||
const presentationsToUpload = newState.filter((p) => !p.uploadCompleted);
|
||||
const presentationsToRemove = oldState.filter((p) => !newState.find((u) => { return u.presentationId === p.presentationId }));
|
||||
|
||||
@ -214,7 +209,7 @@ const persistPresentationChanges = (oldState, newState, uploadEndpoint) => {
|
||||
})
|
||||
.then((presentations) => {
|
||||
if (currentPresentation === undefined) {
|
||||
setPresentation('', POD_ID);
|
||||
setPresentation('');
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@ -229,13 +224,18 @@ const persistPresentationChanges = (oldState, newState, uploadEndpoint) => {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return setPresentation(currentPresentation?.presentationId, POD_ID);
|
||||
return setPresentation(currentPresentation?.presentationId);
|
||||
})
|
||||
.then(removePresentations.bind(null, presentationsToRemove, POD_ID));
|
||||
.then(removePresentations.bind(null, presentationsToRemove, removePresentation));
|
||||
};
|
||||
|
||||
const handleSavePresentation = (
|
||||
presentations = [], isFromPresentationUploaderInterface = true, newPres = {}, currentPresentations = [],
|
||||
presentations = [],
|
||||
isFromPresentationUploaderInterface = true,
|
||||
newPres = {},
|
||||
currentPresentations = [],
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
) => {
|
||||
if (!isPresentationEnabled()) {
|
||||
return null;
|
||||
@ -257,7 +257,8 @@ const handleSavePresentation = (
|
||||
currentPresentations,
|
||||
presentations,
|
||||
PRESENTATION_CONFIG.uploadEndpoint,
|
||||
'DEFAULT_PRESENTATION_POD',
|
||||
setPresentation,
|
||||
removePresentation,
|
||||
);
|
||||
};
|
||||
|
||||
@ -352,8 +353,6 @@ function handleFiledrop(files, files2, that, intl, intlMessages) {
|
||||
export default {
|
||||
handleSavePresentation,
|
||||
persistPresentationChanges,
|
||||
dispatchChangePresentationDownloadable,
|
||||
setPresentation,
|
||||
requestPresentationUploadToken,
|
||||
getExternalUploadData,
|
||||
uploadAndConvertPresentation,
|
||||
|
@ -20,7 +20,6 @@ import {
|
||||
findRemoved,
|
||||
filterInvalidShapes,
|
||||
mapLanguage,
|
||||
sendShapeChanges,
|
||||
usePrevious,
|
||||
} from "./utils";
|
||||
// import { throttle } from "/imports/utils/throttle";
|
||||
@ -85,13 +84,12 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
|
||||
isPresenter,
|
||||
removeShapes,
|
||||
initDefaultPages,
|
||||
persistShape,
|
||||
persistShapeWrapper,
|
||||
shapes,
|
||||
assets,
|
||||
currentUser,
|
||||
whiteboardId,
|
||||
zoomSlide,
|
||||
skipToSlide,
|
||||
curPageId,
|
||||
zoomChanger,
|
||||
isMultiUserActive,
|
||||
@ -675,7 +673,7 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
|
||||
|
||||
console.log("EDITOR : ", editor);
|
||||
|
||||
const debouncePersistShape = debounce({ delay: 0 }, persistShape);
|
||||
const debouncePersistShape = debounce({ delay: 0 }, persistShapeWrapper);
|
||||
|
||||
const colorStyles = ['black', 'blue', 'green', 'grey', 'light-blue', 'light-green', 'light-red', 'light-violet', 'orange', 'red', 'violet', 'yellow'];
|
||||
const dashStyles = ['dashed', 'dotted', 'draw', 'solid'];
|
||||
@ -712,7 +710,7 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
|
||||
createdBy: currentUser?.userId,
|
||||
},
|
||||
};
|
||||
persistShape(updatedRecord, whiteboardId, isModerator);
|
||||
persistShapeWrapper(updatedRecord, whiteboardId, isModerator);
|
||||
});
|
||||
|
||||
Object.values(updated).forEach(([_, record]) => {
|
||||
@ -723,11 +721,11 @@ export default Whiteboard = React.memo(function Whiteboard(props) {
|
||||
createdBy: shapes[record?.id]?.meta?.createdBy,
|
||||
},
|
||||
};
|
||||
persistShape(updatedRecord, whiteboardId, isModerator);
|
||||
persistShapeWrapper(updatedRecord, whiteboardId, isModerator);
|
||||
});
|
||||
|
||||
Object.values(removed).forEach((record) => {
|
||||
removeShapes([record.id], whiteboardId);
|
||||
removeShapes([record.id]);
|
||||
});
|
||||
},
|
||||
{ source: "user", scope: "document" }
|
||||
@ -897,7 +895,7 @@ Whiteboard.propTypes = {
|
||||
isIphone: PropTypes.bool.isRequired,
|
||||
removeShapes: PropTypes.func.isRequired,
|
||||
initDefaultPages: PropTypes.func.isRequired,
|
||||
persistShape: PropTypes.func.isRequired,
|
||||
persistShapeWrapper: PropTypes.func.isRequired,
|
||||
notifyNotAllowedChange: PropTypes.func.isRequired,
|
||||
shapes: PropTypes.objectOf(PropTypes.shape).isRequired,
|
||||
assets: PropTypes.objectOf(PropTypes.shape).isRequired,
|
||||
@ -906,7 +904,6 @@ Whiteboard.propTypes = {
|
||||
}).isRequired,
|
||||
whiteboardId: PropTypes.string,
|
||||
zoomSlide: PropTypes.func.isRequired,
|
||||
skipToSlide: PropTypes.func.isRequired,
|
||||
curPageId: PropTypes.string.isRequired,
|
||||
presentationWidth: PropTypes.number.isRequired,
|
||||
presentationHeight: PropTypes.number.isRequired,
|
||||
@ -940,9 +937,7 @@ Whiteboard.propTypes = {
|
||||
fullscreenAction: PropTypes.string.isRequired,
|
||||
fullscreenRef: PropTypes.instanceOf(Element),
|
||||
handleToggleFullScreen: PropTypes.func.isRequired,
|
||||
nextSlide: PropTypes.func.isRequired,
|
||||
numberOfSlides: PropTypes.number.isRequired,
|
||||
previousSlide: PropTypes.func.isRequired,
|
||||
sidebarNavigationWidth: PropTypes.number,
|
||||
presentationId: PropTypes.string,
|
||||
};
|
||||
|
@ -9,15 +9,12 @@ import { CURSOR_SUBSCRIPTION } from './cursors/queries';
|
||||
import {
|
||||
initDefaultPages,
|
||||
persistShape,
|
||||
removeShapes,
|
||||
changeCurrentSlide,
|
||||
notifyNotAllowedChange,
|
||||
notifyShapeNumberExceeded,
|
||||
toggleToolsAnimations,
|
||||
formatAnnotations,
|
||||
} from './service';
|
||||
import CursorService from './cursors/service';
|
||||
import PresentationToolbarService from '../presentation/presentation-toolbar/service';
|
||||
import SettingsService from '/imports/ui/services/settings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import {
|
||||
@ -34,7 +31,11 @@ import useMeeting from '/imports/ui/core/hooks/useMeeting';
|
||||
import {
|
||||
AssetRecordType,
|
||||
} from "@tldraw/tldraw";
|
||||
import { PRESENTATION_SET_ZOOM } from '../presentation/mutations';
|
||||
import {
|
||||
PRESENTATION_SET_ZOOM,
|
||||
PRES_ANNOTATION_DELETE,
|
||||
PRES_ANNOTATION_SUBMIT,
|
||||
} from '../presentation/mutations';
|
||||
|
||||
const WHITEBOARD_CONFIG = Meteor.settings.public.whiteboard;
|
||||
|
||||
@ -65,6 +66,17 @@ const WhiteboardContainer = (props) => {
|
||||
const hasWBAccess = whiteboardWriters?.some((writer) => writer.userId === Auth.userID);
|
||||
|
||||
const [presentationSetZoom] = useMutation(PRESENTATION_SET_ZOOM);
|
||||
const [presentationDeleteAnnotations] = useMutation(PRES_ANNOTATION_DELETE);
|
||||
const [presentationSubmitAnnotations] = useMutation(PRES_ANNOTATION_SUBMIT);
|
||||
|
||||
const removeShapes = (shapeIds) => {
|
||||
presentationDeleteAnnotations({
|
||||
variables: {
|
||||
pageId: currentPresentationPage?.pageId,
|
||||
annotationsIds: shapeIds,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const zoomSlide = (widthRatio, heightRatio, xOffset, yOffset) => {
|
||||
const { pageId, num } = currentPresentationPage;
|
||||
@ -82,6 +94,21 @@ const WhiteboardContainer = (props) => {
|
||||
});
|
||||
};
|
||||
|
||||
const submitAnnotations = async (newAnnotations) => {
|
||||
const isAnnotationSent = await presentationSubmitAnnotations({
|
||||
variables: {
|
||||
pageId: currentPresentationPage?.pageId,
|
||||
annotations: newAnnotations,
|
||||
},
|
||||
});
|
||||
|
||||
return isAnnotationSent?.data?.presAnnotationSubmit;
|
||||
};
|
||||
|
||||
const persistShapeWrapper = (shape, whiteboardId, isModerator) => {
|
||||
persistShape(shape, whiteboardId, isModerator, submitAnnotations);
|
||||
};
|
||||
|
||||
const isMultiUserActive = whiteboardWriters?.length > 0;
|
||||
|
||||
const { data: pollData } = useSubscription(POLL_RESULTS_SUBSCRIPTION);
|
||||
@ -223,17 +250,13 @@ const WhiteboardContainer = (props) => {
|
||||
sidebarNavigationWidth,
|
||||
layoutContextDispatch,
|
||||
initDefaultPages,
|
||||
persistShape,
|
||||
persistShapeWrapper,
|
||||
isMultiUserActive,
|
||||
changeCurrentSlide,
|
||||
shapes,
|
||||
bgShape,
|
||||
assets,
|
||||
removeShapes,
|
||||
zoomSlide,
|
||||
skipToSlide: PresentationToolbarService.skipToSlide,
|
||||
nextSlide: PresentationToolbarService.nextSlide,
|
||||
previousSlide: PresentationToolbarService.previousSlide,
|
||||
numberOfSlides: currentPresentationPage?.totalPages,
|
||||
notifyNotAllowedChange,
|
||||
notifyShapeNumberExceeded,
|
||||
|
@ -1,11 +1,9 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
import PollService from '/imports/ui/components/poll/service';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import caseInsensitiveReducer from '/imports/utils/caseInsensitiveReducer';
|
||||
import { getTextSize } from './utils';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
notifyNotAllowedChange: {
|
||||
@ -30,7 +28,7 @@ const annotationsRetryDelay = 1000;
|
||||
|
||||
let annotationsSenderIsRunning = false;
|
||||
|
||||
const proccessAnnotationsQueue = async () => {
|
||||
const proccessAnnotationsQueue = async (submitAnnotations) => {
|
||||
annotationsSenderIsRunning = true;
|
||||
const queueSize = annotationsQueue.length;
|
||||
|
||||
@ -41,24 +39,29 @@ const proccessAnnotationsQueue = async () => {
|
||||
|
||||
const annotations = annotationsQueue.splice(0, queueSize);
|
||||
|
||||
const isAnnotationSent = await makeCall('sendBulkAnnotations', annotations);
|
||||
try {
|
||||
const isAnnotationSent = await submitAnnotations(annotations);
|
||||
|
||||
if (!isAnnotationSent) {
|
||||
// undo splice
|
||||
if (!isAnnotationSent) {
|
||||
// undo splice
|
||||
annotationsQueue.splice(0, 0, ...annotations);
|
||||
setTimeout(() => proccessAnnotationsQueue(submitAnnotations), annotationsRetryDelay);
|
||||
} else {
|
||||
// ask tiago
|
||||
const delayPerc = Math.min(
|
||||
annotationsMaxDelayQueueSize, queueSize,
|
||||
) / annotationsMaxDelayQueueSize;
|
||||
const delayDelta = annotationsBufferTimeMax - annotationsBufferTimeMin;
|
||||
const delayTime = annotationsBufferTimeMin + delayDelta * delayPerc;
|
||||
setTimeout(() => proccessAnnotationsQueue(submitAnnotations), delayTime);
|
||||
}
|
||||
} catch (error) {
|
||||
annotationsQueue.splice(0, 0, ...annotations);
|
||||
setTimeout(proccessAnnotationsQueue, annotationsRetryDelay);
|
||||
} else {
|
||||
// ask tiago
|
||||
const delayPerc = Math.min(
|
||||
annotationsMaxDelayQueueSize, queueSize,
|
||||
) / annotationsMaxDelayQueueSize;
|
||||
const delayDelta = annotationsBufferTimeMax - annotationsBufferTimeMin;
|
||||
const delayTime = annotationsBufferTimeMin + delayDelta * delayPerc;
|
||||
setTimeout(proccessAnnotationsQueue, delayTime);
|
||||
setTimeout(() => proccessAnnotationsQueue(submitAnnotations), annotationsRetryDelay);
|
||||
}
|
||||
};
|
||||
|
||||
const sendAnnotation = (annotation) => {
|
||||
const sendAnnotation = (annotation, submitAnnotations) => {
|
||||
// Prevent sending annotations while disconnected
|
||||
// TODO: Change this to add the annotation, but delay the send until we're
|
||||
// reconnected. With this it will miss things
|
||||
@ -70,7 +73,7 @@ const sendAnnotation = (annotation) => {
|
||||
} else {
|
||||
annotationsQueue.push(annotation);
|
||||
}
|
||||
if (!annotationsSenderIsRunning) setTimeout(proccessAnnotationsQueue, annotationsBufferTimeMin);
|
||||
if (!annotationsSenderIsRunning) setTimeout(() => proccessAnnotationsQueue(submitAnnotations), annotationsBufferTimeMin);
|
||||
};
|
||||
|
||||
const getMultiUser = (whiteboardId) => {
|
||||
@ -87,7 +90,7 @@ const getMultiUser = (whiteboardId) => {
|
||||
return data.multiUser;
|
||||
};
|
||||
|
||||
const persistShape = (shape, whiteboardId, isModerator) => {
|
||||
const persistShape = async (shape, whiteboardId, isModerator, submitAnnotations) => {
|
||||
const annotation = {
|
||||
id: shape.id,
|
||||
annotationInfo: { ...shape, isModerator },
|
||||
@ -95,13 +98,7 @@ const persistShape = (shape, whiteboardId, isModerator) => {
|
||||
userId: Auth.userID,
|
||||
};
|
||||
|
||||
sendAnnotation(annotation);
|
||||
};
|
||||
|
||||
const removeShapes = (shapes, whiteboardId) => makeCall('deleteAnnotations', shapes, whiteboardId);
|
||||
|
||||
const changeCurrentSlide = (s) => {
|
||||
makeCall('changeCurrentSlide', s);
|
||||
sendAnnotation(annotation, submitAnnotations);
|
||||
};
|
||||
|
||||
const initDefaultPages = (count = 1) => {
|
||||
@ -283,8 +280,6 @@ export {
|
||||
sendAnnotation,
|
||||
getMultiUser,
|
||||
persistShape,
|
||||
removeShapes,
|
||||
changeCurrentSlide,
|
||||
notifyNotAllowedChange,
|
||||
notifyShapeNumberExceeded,
|
||||
toggleToolsAnimations,
|
||||
|
@ -1,11 +1,4 @@
|
||||
import React from 'react';
|
||||
import { isEqual } from 'radash';
|
||||
import {
|
||||
persistShape,
|
||||
removeShapes,
|
||||
notifyNotAllowedChange,
|
||||
notifyShapeNumberExceeded,
|
||||
} from './service';
|
||||
|
||||
const WHITEBOARD_CONFIG = Meteor.settings.public.whiteboard;
|
||||
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
|
||||
@ -90,118 +83,6 @@ const isValidShapeType = (shape) => {
|
||||
return !invalidTypes.includes(shape?.type);
|
||||
};
|
||||
|
||||
const sendShapeChanges = (
|
||||
app,
|
||||
changedShapes,
|
||||
shapes,
|
||||
prevShapes,
|
||||
hasShapeAccess,
|
||||
whiteboardId,
|
||||
currentUser,
|
||||
intl,
|
||||
redo = false,
|
||||
) => {
|
||||
let isModerator = currentUser?.role === ROLE_MODERATOR;
|
||||
|
||||
const invalidChange = Object.keys(changedShapes)
|
||||
.find((id) => !hasShapeAccess(id));
|
||||
|
||||
const invalidShapeType = Object.keys(changedShapes)
|
||||
.find((id) => !isValidShapeType(changedShapes[id]));
|
||||
|
||||
const currentShapes = app?.document?.pages[app?.currentPageId]?.shapes;
|
||||
const { maxNumberOfAnnotations } = WHITEBOARD_CONFIG;
|
||||
// -1 for background shape
|
||||
const shapeNumberExceeded = Object.keys(currentShapes).length - 1 > maxNumberOfAnnotations;
|
||||
|
||||
const isInserting = Object.keys(changedShapes)
|
||||
.filter(
|
||||
(shape) => typeof changedShapes[shape] === 'object'
|
||||
&& changedShapes[shape].type
|
||||
&& !prevShapes[shape],
|
||||
).length !== 0;
|
||||
|
||||
if (invalidChange || invalidShapeType || (shapeNumberExceeded && isInserting)) {
|
||||
if (shapeNumberExceeded) {
|
||||
notifyShapeNumberExceeded(intl, maxNumberOfAnnotations);
|
||||
} else {
|
||||
notifyNotAllowedChange(intl);
|
||||
}
|
||||
const modApp = app;
|
||||
// undo last command without persisting to not generate the onUndo/onRedo callback
|
||||
if (!redo) {
|
||||
const command = app.stack[app.pointer];
|
||||
modApp.pointer -= 1;
|
||||
app.applyPatch(command.before, 'undo');
|
||||
return;
|
||||
// eslint-disable-next-line no-else-return
|
||||
} else {
|
||||
modApp.pointer += 1;
|
||||
const command = app.stack[app.pointer];
|
||||
app.applyPatch(command.after, 'redo');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const deletedShapes = [];
|
||||
Object.entries(changedShapes)
|
||||
.forEach(([id, shape]) => {
|
||||
if (!shape) deletedShapes.push(id);
|
||||
else {
|
||||
// checks to find any bindings assosiated with the changed shapes.
|
||||
// If any, they may need to be updated as well.
|
||||
const pageBindings = app.page.bindings;
|
||||
if (pageBindings) {
|
||||
Object.entries(pageBindings).forEach(([, b]) => {
|
||||
if (b.toId.includes(id)) {
|
||||
const boundShape = app.getShape(b.fromId);
|
||||
if (shapes[b.fromId] && !isEqual(boundShape, shapes[b.fromId])) {
|
||||
const shapeBounds = app.getShapeBounds(b.fromId);
|
||||
boundShape.size = [shapeBounds.width, shapeBounds.height];
|
||||
persistShape(boundShape, whiteboardId, isModerator);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
let modShape = shape;
|
||||
if (!shape.id) {
|
||||
// check it already exists (otherwise we need the full shape)
|
||||
if (!shapes[id]) {
|
||||
modShape = app.getShape(id);
|
||||
}
|
||||
modShape.id = id;
|
||||
}
|
||||
const shapeBounds = app.getShapeBounds(id);
|
||||
const size = [shapeBounds.width, shapeBounds.height];
|
||||
if (!shapes[id] || (shapes[id] && !isEqual(shapes[id].size, size))) {
|
||||
modShape.size = size;
|
||||
}
|
||||
if (!shapes[id] || (shapes[id] && !shapes[id].userId)) {
|
||||
modShape.userId = currentUser?.userId;
|
||||
}
|
||||
// do not change moderator status for existing shapes
|
||||
if (shapes[id]) {
|
||||
isModerator = shapes[id].isModerator;
|
||||
}
|
||||
persistShape(modShape, whiteboardId, isModerator);
|
||||
}
|
||||
});
|
||||
|
||||
// order the ids of shapes being deleted to prevent crash
|
||||
// when removing a group shape before its children
|
||||
const orderedDeletedShapes = [];
|
||||
deletedShapes.forEach((eid) => {
|
||||
if (shapes[eid]?.type !== 'group') {
|
||||
orderedDeletedShapes.unshift(eid);
|
||||
} else {
|
||||
orderedDeletedShapes.push(eid);
|
||||
}
|
||||
});
|
||||
|
||||
if (orderedDeletedShapes.length > 0) {
|
||||
removeShapes(orderedDeletedShapes, whiteboardId);
|
||||
}
|
||||
};
|
||||
|
||||
// map different localeCodes from bbb to tldraw
|
||||
const mapLanguage = (language) => {
|
||||
// bbb has xx-xx but in tldraw it's only xx
|
||||
@ -276,10 +157,10 @@ const getTextSize = (text, style, padding) => {
|
||||
};
|
||||
|
||||
const Utils = {
|
||||
usePrevious, findRemoved, filterInvalidShapes, mapLanguage, sendShapeChanges, getTextSize,
|
||||
usePrevious, findRemoved, filterInvalidShapes, mapLanguage, getTextSize,
|
||||
};
|
||||
|
||||
export default Utils;
|
||||
export {
|
||||
usePrevious, findRemoved, filterInvalidShapes, mapLanguage, sendShapeChanges, getTextSize,
|
||||
usePrevious, findRemoved, filterInvalidShapes, mapLanguage, getTextSize,
|
||||
};
|
||||
|
@ -3,13 +3,10 @@ import '/imports/startup/server';
|
||||
// 2x
|
||||
import '/imports/api/meetings/server';
|
||||
import '/imports/api/users/server';
|
||||
import '/imports/api/annotations/server';
|
||||
import '/imports/api/cursor/server';
|
||||
import '/imports/api/polls/server';
|
||||
import '/imports/api/captions/server';
|
||||
import '/imports/api/presentations/server';
|
||||
import '/imports/api/presentation-upload-token/server';
|
||||
import '/imports/api/slides/server';
|
||||
import '/imports/api/breakouts/server';
|
||||
import '/imports/api/breakouts-history/server';
|
||||
import '/imports/api/screenshare/server';
|
||||
|
Loading…
Reference in New Issue
Block a user