Merge branch 'v2.6.x-release' into tldraw-with-annotation-export

This commit is contained in:
Daniel Petri Rocha 2022-05-24 10:33:04 +02:00
commit 745034da12
22 changed files with 269 additions and 5637 deletions

View File

@ -108,17 +108,12 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
#TD-Tools > div {
flex-direction: column;
}
#TD-Styles + div {
display: none;
}
</style>
<script>
document.addEventListener('gesturestart', function (e) {
e.preventDefault();
});
</script>
<script src="compatibility/adapter.js?v=VERSION" language="javascript"></script>
<script src="compatibility/sip.js?v=VERSION" language="javascript"></script>
<script src="compatibility/kurento-utils.js?v=VERSION" language="javascript"></script>
<script src="compatibility/tflite-simd.js?v=VERSION" language="javascript"></script>

View File

@ -31,6 +31,9 @@ import ChatAdapter from '/imports/ui/components/components-data/chat-context/ada
import UsersAdapter from '/imports/ui/components/components-data/users-context/adapter';
import GroupChatAdapter from '/imports/ui/components/components-data/group-chat-context/adapter';
import { liveDataEventBrokerInitializer } from '/imports/ui/services/LiveDataEventBroker/LiveDataEventBroker';
// The adapter import is "unused" as far as static code is concerned, but it
// needs to here to override global prototypes. So: don't remove it - prlanzarin 25 Apr 2022
import adapter from 'webrtc-adapter';
import collectionMirrorInitializer from './collection-mirror-initializer';

View File

@ -30,11 +30,9 @@ const process = () => {
};
export default function handleWhiteboardSend({ header, body }, meetingId) {
//console.log("!!!!!!!!!! handleWhiteboardSend!!!!!!!!!!: ");
const userId = header.userId;
const whiteboardId = body.whiteboardId;
const annotations = body.annotations;
//console.log(annotations);
check(userId, String);
check(whiteboardId, String);
@ -45,13 +43,11 @@ export default function handleWhiteboardSend({ header, body }, meetingId) {
}
annotations.map(annotation => {
annotationsQueue[meetingId].push({ meetingId, whiteboardId, userId, annotation });
addAnnotation(meetingId, whiteboardId, userId, annotation);
annotationsQueue[meetingId].push({ meetingId, whiteboardId, userId: annotation.userId, annotation });
addAnnotation(meetingId, whiteboardId, annotation.userId, annotation);
})
if (queueMetrics) {
Metrics.setAnnotationQueueLength(meetingId, annotationsQueue[meetingId].length);
}
if (!annotationsRecieverIsRunning) process();
//return addAnnotation(meetingId, whiteboardId, userId, annotation);
}

View File

@ -22,13 +22,6 @@ export default function deleteAnnotations(annotations, whiteboardId) {
annotationsIds: annotations,
};
console.log('$$$$$$$$$$$ shapes to remove $$$$$$$$$$$$$$$$$$$$$$')
console.log('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$')
console.log(annotations)
console.log('$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$')
// shape.meetingId = meetingId;
//deleteShape(shape);
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
} catch (err) {
Logger.error(`Exception while invoking method deleteAnnotation ${err.stack}`);

View File

@ -23,9 +23,6 @@ export default function sendAnnotationHelper(annotations, meetingId, requesterUs
annotations: whiteboardAnnotations,
};
//console.log("AEAFAEWFEWFWE")
//console.log(annotations)
RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
});

View File

@ -5,7 +5,6 @@ import transferUser from './methods/transferUser';
import toggleLockSettings from './methods/toggleLockSettings';
import toggleWebcamsOnlyForModerator from './methods/toggleWebcamsOnlyForModerator';
import clearRandomlySelectedUser from './methods/clearRandomlySelectedUser';
import changeCurrentSlide from './methods/changeCurrentSlide';
import changeLayout from './methods/changeLayout';
Meteor.methods({
@ -16,5 +15,4 @@ Meteor.methods({
toggleWebcamsOnlyForModerator,
clearRandomlySelectedUser,
changeLayout,
changeCurrentSlide,
});

View File

@ -1,31 +0,0 @@
import Logger from '/imports/startup/server/logger';
import RedisPubSub from '/imports/startup/server/redis';
import { extractCredentials } from '/imports/api/common/server/helpers';
import { check } from 'meteor/check';
import setCurrentSlide from '../modifiers/setCurrentSlide';
export default function changeCurrentSlide(slideNum) {
const REDIS_CONFIG = Meteor.settings.private.redis;
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
const EVENT_NAME = 'BroadcastLayoutMsg';
console.log('$$$$$$$$$$$$$$$$$$$')
console.log(slideNum)
console.log('$$$$$$$$$$$$$$$$$$$')
try {
const { meetingId, requesterUserId } = extractCredentials(this.userId);
setCurrentSlide(meetingId, slideNum);
// check(meetingId, String);
// check(requesterUserId, String);
// const payload = {
// layout,
// };
// RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
} catch (err) {
// Logger.error(`Exception while invoking method changeLayout ${err.stack}`);
}
}

View File

@ -1,25 +0,0 @@
import Logger from '/imports/startup/server/logger';
import Meetings from '/imports/api/meetings';
import { check } from 'meteor/check';
export default function setCurrentSlide(meetingId, slideNum) {
try {
const selector = {
meetingId,
};
const modifier = {
$set: {
activeSlide: slideNum,
},
};
const activeUpdate = Meetings.update(selector, modifier);
if (activeUpdate) {
Logger.info(`Meeting active slide changed for meeting=${meetingId}`);
}
} catch (err) {
Logger.error(`Exception while invoking method setCurrentSlide ${err.stack}`);
}
}

View File

@ -1,12 +1,3 @@
// import { Meteor } from 'meteor/meteor';
// import switchSlide from './methods/switchSlide';
// import zoomSlide from './methods/zoomSlide';
// Meteor.methods({
// switchSlide,
// zoomSlide,
// });
import { Meteor } from 'meteor/meteor';
import switchSlide from './methods/switchSlide';
import zoomSlide from './methods/zoomSlide';
@ -16,4 +7,4 @@ Meteor.methods({
switchSlide,
zoomSlide,
persistAsset,
});
});

View File

@ -101,17 +101,16 @@ export default function addSlide(meetingId, podId, presentationId, slide) {
const slideData = {
width,
height,
x: xCamera,
y: yCamera,
xCamera,
yCamera,
zoom,
};
const slidePosition = calculateSlideData(slideData);
//const slidePosition = calculateSlideData(slideData);
addSlidePositions(meetingId, podId, presentationId, slideId, slideData);
}
try {
console.log("modifier!!! ", modifier )
const { insertedId, numberAffected } = Slides.upsert(selector, modifier);
requestWhiteboardHistory(meetingId, slideId);

View File

@ -18,8 +18,8 @@ export default function addSlidePositions(
check(slidePosition, {
width: Number,
height: Number,
x: Number,
y: Number,
xCamera: Number,
yCamera: Number,
zoom: Number,
});

View File

@ -931,7 +931,8 @@ class Presentation extends PureComponent {
slidePosition={slidePosition}
getSvgRef={this.getSvgRef}
setTldrawAPI={this.setTldrawAPI}
curPageId={this.state.tldrawAPI?.getPage()?.id}
curPageId={currentSlide?.num.toString()}
svgUri={currentSlide?.svgUri}
/>
{isFullscreen && <PollingContainer />}
{this.renderPresentationToolbar()}

View File

@ -1,78 +1,74 @@
import React, { PureComponent } from "react";
import PropTypes from "prop-types";
import { defineMessages, injectIntl } from "react-intl";
import deviceInfo from "/imports/utils/deviceInfo";
import injectWbResizeEvent from "/imports/ui/components/presentation/resize-wrapper/component";
import {
HUNDRED_PERCENT,
MAX_PERCENT,
STEP,
} from "/imports/utils/slideCalcUtils";
import Styled from "./styles";
import ZoomTool from "./zoom-tool/component";
import TooltipContainer from "/imports/ui/components/common/tooltip/container";
import KEY_CODES from "/imports/utils/keyCodes";
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import deviceInfo from '/imports/utils/deviceInfo';
import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
import { HUNDRED_PERCENT, MAX_PERCENT, STEP } from '/imports/utils/slideCalcUtils';
import Styled from './styles';
import ZoomTool from './zoom-tool/component';
import TooltipContainer from '/imports/ui/components/common/tooltip/container';
import KEY_CODES from '/imports/utils/keyCodes';
const intlMessages = defineMessages({
previousSlideLabel: {
id: "app.presentation.presentationToolbar.prevSlideLabel",
description: "Previous slide button label",
id: 'app.presentation.presentationToolbar.prevSlideLabel',
description: 'Previous slide button label',
},
previousSlideDesc: {
id: "app.presentation.presentationToolbar.prevSlideDesc",
description: "Aria description for when switching to previous slide",
id: 'app.presentation.presentationToolbar.prevSlideDesc',
description: 'Aria description for when switching to previous slide',
},
nextSlideLabel: {
id: "app.presentation.presentationToolbar.nextSlideLabel",
description: "Next slide button label",
id: 'app.presentation.presentationToolbar.nextSlideLabel',
description: 'Next slide button label',
},
nextSlideDesc: {
id: "app.presentation.presentationToolbar.nextSlideDesc",
description: "Aria description for when switching to next slide",
id: 'app.presentation.presentationToolbar.nextSlideDesc',
description: 'Aria description for when switching to next slide',
},
noNextSlideDesc: {
id: "app.presentation.presentationToolbar.noNextSlideDesc",
description: "",
id: 'app.presentation.presentationToolbar.noNextSlideDesc',
description: '',
},
noPrevSlideDesc: {
id: "app.presentation.presentationToolbar.noPrevSlideDesc",
description: "",
id: 'app.presentation.presentationToolbar.noPrevSlideDesc',
description: '',
},
skipSlideLabel: {
id: "app.presentation.presentationToolbar.skipSlideLabel",
description: "Aria label for when switching to a specific slide",
id: 'app.presentation.presentationToolbar.skipSlideLabel',
description: 'Aria label for when switching to a specific slide',
},
skipSlideDesc: {
id: "app.presentation.presentationToolbar.skipSlideDesc",
description: "Aria description for when switching to a specific slide",
id: 'app.presentation.presentationToolbar.skipSlideDesc',
description: 'Aria description for when switching to a specific slide',
},
goToSlide: {
id: "app.presentation.presentationToolbar.goToSlide",
description: "button for slide select",
id: 'app.presentation.presentationToolbar.goToSlide',
description: 'button for slide select',
},
selectLabel: {
id: "app.presentation.presentationToolbar.selectLabel",
description: "slide select label",
id: 'app.presentation.presentationToolbar.selectLabel',
description: 'slide select label',
},
fitToWidth: {
id: "app.presentation.presentationToolbar.fitToWidth",
description: "button for fit to width",
id: 'app.presentation.presentationToolbar.fitToWidth',
description: 'button for fit to width',
},
fitToWidthDesc: {
id: "app.presentation.presentationToolbar.fitWidthDesc",
description: "Aria description to display the whole width of the slide",
id: 'app.presentation.presentationToolbar.fitWidthDesc',
description: 'Aria description to display the whole width of the slide',
},
fitToPage: {
id: "app.presentation.presentationToolbar.fitToPage",
description: "button label for fit to width",
id: 'app.presentation.presentationToolbar.fitToPage',
description: 'button label for fit to width',
},
fitToPageDesc: {
id: "app.presentation.presentationToolbar.fitScreenDesc",
description: "Aria description to display the whole slide",
id: 'app.presentation.presentationToolbar.fitScreenDesc',
description: 'Aria description to display the whole slide',
},
presentationLabel: {
id: "app.presentationUploder.title",
description: "presentation area element label",
id: 'app.presentationUploder.title',
description: 'presentation area element label',
},
});
@ -80,10 +76,6 @@ class PresentationToolbar extends PureComponent {
constructor(props) {
super(props);
this.state = {
curPageId: 1,
};
this.handleSkipToSlideChange = this.handleSkipToSlideChange.bind(this);
this.change = this.change.bind(this);
this.renderAriaDescs = this.renderAriaDescs.bind(this);
@ -94,16 +86,16 @@ class PresentationToolbar extends PureComponent {
}
componentDidMount() {
document.addEventListener("keydown", this.switchSlide);
document.addEventListener('keydown', this.switchSlide);
}
componentWillUnmount() {
document.removeEventListener("keydown", this.switchSlide);
document.removeEventListener('keydown', this.switchSlide);
}
switchSlide(event) {
const { target, which } = event;
const isBody = target.nodeName === "BODY";
const isBody = target.nodeName === 'BODY';
if (isBody) {
switch (which) {
@ -124,26 +116,34 @@ class PresentationToolbar extends PureComponent {
}
handleSkipToSlideChange(event) {
const { skipToSlide, podId } = this.props;
const {
skipToSlide,
podId,
} = this.props;
const requestedSlideNum = Number.parseInt(event.target.value, 10);
if (event) event.currentTarget.blur();
// skipToSlide(requestedSlideNum, podId);
this.props?.tldrawAPI?.changePage(requestedSlideNum);
this.setState({
curPageId: parseInt(this.props?.tldrawAPI?.getPage()?.id),
});
skipToSlide(requestedSlideNum, podId);
}
nextSlideHandler(event) {
const { nextSlide, currentSlideNum, numberOfSlides, podId } = this.props;
const {
nextSlide,
currentSlideNum,
numberOfSlides,
podId,
} = this.props;
if (event) event.currentTarget.blur();
nextSlide(currentSlideNum, numberOfSlides, podId);
}
previousSlideHandler(event) {
const { previousSlide, currentSlideNum, podId } = this.props;
const {
previousSlide,
currentSlideNum,
podId,
} = this.props;
if (event) event.currentTarget.blur();
previousSlide(currentSlideNum, podId);
@ -160,13 +160,13 @@ class PresentationToolbar extends PureComponent {
} = this.props;
handleToggleFullScreen(fullscreenRef);
const newElement = isFullscreen ? "" : fullscreenElementId;
const newElement = isFullscreen ? '' : fullscreenElementId;
layoutContextDispatch({
type: fullscreenAction,
value: {
element: newElement,
group: "",
group: '',
},
});
}
@ -211,11 +211,15 @@ class PresentationToolbar extends PureComponent {
const { intl } = this.props;
const optionList = [];
for (let i = 1; i <= numberOfSlides; i += 1) {
optionList.push(
<option value={i} key={i}>
{intl.formatMessage(intlMessages.goToSlide, { 0: i })}
</option>
);
optionList.push((
<option
value={i}
key={i}
>
{
intl.formatMessage(intlMessages.goToSlide, { 0: i })
}
</option>));
}
return optionList;
@ -241,47 +245,42 @@ class PresentationToolbar extends PureComponent {
const { isMobile } = deviceInfo;
const startOfSlides = parseInt(this.state.curPageId) === 1;
const endOfSlides = parseInt(this.state.curPageId) === numberOfSlides;
const startOfSlides = !(currentSlideNum > 1);
const endOfSlides = !(currentSlideNum < numberOfSlides);
const prevSlideAriaLabel = startOfSlides
? intl.formatMessage(intlMessages.previousSlideLabel)
: `${intl.formatMessage(intlMessages.previousSlideLabel)} (${
parseInt(this.props?.tldrawAPI?.getPage()?.id) <= 1
? ""
: parseInt(this.props?.tldrawAPI?.getPage()?.id) - 1
})`;
: `${intl.formatMessage(intlMessages.previousSlideLabel)} (${currentSlideNum <= 1 ? '' : (currentSlideNum - 1)})`;
const nextSlideAriaLabel = endOfSlides
? intl.formatMessage(intlMessages.nextSlideLabel)
: `${intl.formatMessage(intlMessages.nextSlideLabel)} (${
parseInt(this.props?.tldrawAPI?.getPage()?.id) >= 1
? parseInt(this.props?.tldrawAPI?.getPage()?.id) + 1
: ""
})`;
: `${intl.formatMessage(intlMessages.nextSlideLabel)} (${currentSlideNum >= 1 ? (currentSlideNum + 1) : ''})`;
return (
<Styled.PresentationToolbarWrapper
id="presentationToolbarWrapper"
style={{
width: "100%",
}}
>
style={
{
width: toolbarWidth,
}
}>
{this.renderAriaDescs()}
{
<div>
{isPollingEnabled ? (
<Styled.QuickPollButton
{...{
currentSlidHasContent,
intl,
amIPresenter,
parseCurrentSlideContent,
startPoll,
currentSlide,
}}
/>
) : null}
{isPollingEnabled
? (
<Styled.QuickPollButton
{...{
currentSlidHasContent,
intl,
amIPresenter,
parseCurrentSlideContent,
startPoll,
currentSlide,
}}
/>
) : null
}
</div>
}
{
@ -289,29 +288,18 @@ class PresentationToolbar extends PureComponent {
<Styled.PrevSlideButton
role="button"
aria-label={prevSlideAriaLabel}
aria-describedby={
startOfSlides ? "noPrevSlideDesc" : "prevSlideDesc"
}
aria-describedby={startOfSlides ? 'noPrevSlideDesc' : 'prevSlideDesc'}
disabled={startOfSlides || !isMeteorConnected}
color="default"
icon="left_arrow"
size="md"
onClick={() => {
this.props?.tldrawAPI?.changePage(
parseInt(this.props?.tldrawAPI?.getPage()?.id) - 1
);
this.setState({
curPageId: parseInt(this.props?.tldrawAPI?.getPage()?.id),
});
}}
onClick={this.previousSlideHandler}
label={intl.formatMessage(intlMessages.previousSlideLabel)}
hideLabel
data-test="prevSlide"
/>
<TooltipContainer
title={intl.formatMessage(intlMessages.selectLabel)}
>
<TooltipContainer title={intl.formatMessage(intlMessages.selectLabel)}>
<Styled.SkipSlideSelect
id="skipSlide"
aria-label={intl.formatMessage(intlMessages.skipSlideLabel)}
@ -319,7 +307,7 @@ class PresentationToolbar extends PureComponent {
aria-live="polite"
aria-relevant="all"
disabled={!isMeteorConnected}
value={this.state.curPageId}
value={currentSlideNum}
onChange={this.handleSkipToSlideChange}
data-test="skipSlide"
>
@ -329,21 +317,12 @@ class PresentationToolbar extends PureComponent {
<Styled.NextSlideButton
role="button"
aria-label={nextSlideAriaLabel}
aria-describedby={
endOfSlides ? "noNextSlideDesc" : "nextSlideDesc"
}
aria-describedby={endOfSlides ? 'noNextSlideDesc' : 'nextSlideDesc'}
disabled={endOfSlides || !isMeteorConnected}
color="default"
icon="right_arrow"
size="md"
onClick={() => {
this.props?.tldrawAPI?.changePage(
parseInt(this.props?.tldrawAPI?.getPage()?.id) + 1
);
this.setState({
curPageId: parseInt(this.props?.tldrawAPI?.getPage()?.id),
});
}}
onClick={this.nextSlideHandler}
label={intl.formatMessage(intlMessages.nextSlideLabel)}
hideLabel
data-test="nextSlide"
@ -352,12 +331,16 @@ class PresentationToolbar extends PureComponent {
}
{
<Styled.PresentationZoomControls>
{!isMobile ? (
<TooltipContainer>
{
!isMobile
? (
<TooltipContainer>
<ZoomTool
zoomValue={
//this.props?.tldrawAPI?.pageStates[currentSlideNum.toString()]?.camera?.zoom
this.props?.tldrawAPI?.getPageState()?.camera?.zoom
}
currentSlideNum={currentSlideNum}
change={this.change}
minBound={0.1}
maxBound={5}
@ -365,19 +348,16 @@ class PresentationToolbar extends PureComponent {
isMeteorConnected={isMeteorConnected}
tldrawAPI={this.props?.tldrawAPI}
/>
</TooltipContainer>
) : null}
</TooltipContainer>
)
: null
}
<Styled.FitToWidthButton
role="button"
aria-describedby={fitToWidth ? "fitPageDesc" : "fitWidthDesc"}
aria-label={
fitToWidth
? `${intl.formatMessage(
intlMessages.presentationLabel
)} ${intl.formatMessage(intlMessages.fitToPage)}`
: `${intl.formatMessage(
intlMessages.presentationLabel
)} ${intl.formatMessage(intlMessages.fitToWidth)}`
aria-describedby={fitToWidth ? 'fitPageDesc' : 'fitWidthDesc'}
aria-label={fitToWidth
? `${intl.formatMessage(intlMessages.presentationLabel)} ${intl.formatMessage(intlMessages.fitToPage)}`
: `${intl.formatMessage(intlMessages.presentationLabel)} ${intl.formatMessage(intlMessages.fitToWidth)}`
}
color="default"
disabled={!isMeteorConnected}

View File

@ -190,7 +190,7 @@ class ZoomTool extends PureComponent {
aria-describedby="resetZoomDescription"
disabled={(stateZoomValue === minBound) || !isMeteorConnected}
color="default"
customIcon={`${this.props?.tldrawAPI?.getPageState()?.camera?.zoom * 100}%`}
customIcon={`${parseInt(this.props?.tldrawAPI?.getPageState()?.camera?.zoom * 100)}%`}
size="md"
onClick={() => tldrawAPI?.zoomTo(1)}
label={intl.formatMessage(intlMessages.resetZoomLabel)}

View File

@ -32,7 +32,7 @@ class UserContent extends PureComponent {
return (
<Styled.Content data-test="userListContent">
{isChatEnabled() ? <UserMessagesContainer /> : null}
{/* {currentUser.role === ROLE_MODERATOR ? <UserCaptionsContainer /> : null} */}
{currentUser.role === ROLE_MODERATOR ? <UserCaptionsContainer /> : null}
<UserNotesContainer />
{showWaitingRoom && currentUser.role === ROLE_MODERATOR
? (

View File

@ -38,13 +38,13 @@ export default function Whiteboard(props) {
assets,
currentUser,
curPres,
curSlide,
changeCurrentSlide,
whiteboardId,
podId,
zoomSlide,
skipToSlide,
slidePosition,
curPageId,
svgUri,
} = props;
const { pages, pageStates } = initDefaultPages(curPres?.pages.length || 1);
@ -57,138 +57,96 @@ export default function Whiteboard(props) {
bindings: {},
assets,
});
const [doc, setDoc] = React.useState(rDocument.current);
const [curPage, setCurPage] = React.useState({ id: "1" });
//const [doc, setDoc] = React.useState(rDocument.current);
const [_assets, setAssets] = React.useState(assets);
const [command, setCommand] = React.useState("");
const [wbAccess, setWBAccess] = React.useState(props?.hasMultiUserAccess(props.whiteboardId, props.currentUser.userId));
const [selectedIds, setSelectedIds] = React.useState([]);
const [tldrawAPI, setTLDrawAPI] = React.useState(null);
const prevShapes = usePrevious(shapes);
const prevPage = usePrevious(curPage);
const prevSlidePosition = usePrevious(slidePosition);
const prevPageId = usePrevious(curPageId);
const handleChange = React.useCallback((state, reason) => {
rDocument.current = state.document;
}, []);
React.useMemo(() => {
const doc = React.useMemo(() => {
const currentDoc = rDocument.current;
const propShapes = Object.entries(shapes.filter(s => s.parentId === tldrawAPI?.getPage()?.id) || {})?.map(([k, v]) => v.id);
if (tldrawAPI) {
tldrawAPI?.getPage()?.id && tldrawAPI.changePage(tldrawAPI?.getPage()?.id);
let next = { ...currentDoc };
let pageBindings = null;
let history = null;
let stack = null;
let changed = false;
if (next.pageStates[curPageId] && !_.isEqual(prevShapes, shapes)) {
// mergeDocument loses bindings and history, save it
pageBindings = tldrawAPI?.getPage(curPageId)?.bindings;
history = tldrawAPI?.history
stack = tldrawAPI?.stack
next.pages[curPageId].shapes = shapes;
changed = true;
}
const next = { ...currentDoc };
if (next.pages[curPageId] && !next.pages[curPageId].shapes["slide-background-shape"]) {
next.assets[`slide-background-asset-${curPageId}`] = {
id: `slide-background-asset-${curPageId}`,
size: [slidePosition?.width || 0, slidePosition?.height || 0],
src: svgUri,
type: "image",
};
next.assets = { ...assets };
next.pages[curPageId].shapes["slide-background-shape"] = {
assetId: `slide-background-asset-${curPageId}`,
childIndex: 1,
id: "slide-background-shape",
name: "Image",
type: TDShapeType.Image,
parentId: `${curPageId}`,
point: [0, 0],
isLocked: true,
size: [slidePosition?.width || 0, slidePosition?.height || 0],
style: {
dash: DashStyle.Draw,
size: SizeStyle.Medium,
color: ColorStyle.Blue,
},
};
const pShapes = Object.entries(shapes || {})?.map(([k, v]) => v.id);
shapes.filter(s => s.parentId === tldrawAPI?.getPage()?.id)?.forEach((s) => {
try {
Object.keys(next.pages[s.parentId].shapes).forEach((k) => {
if (!pShapes.includes(k) && s.parentId === tldrawAPI?.getPage()?.id) {
delete next.pages[s.parentId].shapes[k];
}
});
next.pages[s.parentId] = {
...next.pages[s.parentId],
shapes: {
...next.pages[s.parentId].shapes,
[s.id]: { ...s },
},
};
} catch (err) {
changed = true;
}
if (changed) {
tldrawAPI?.mergeDocument(next);
if (tldrawAPI && history) tldrawAPI.history = history;
if (tldrawAPI && stack) tldrawAPI.stack = stack;
if (pageBindings && Object.keys(pageBindings).length !== 0) {
currentDoc.pages[curPageId].bindings = pageBindings;
}
});
if (curPres?.pages.length) {
curPres.pages.map((p, i) => {
next.assets[`slide-background-asset-${i}`] = {
id: `slide-background-asset-${i}`,
size: [slidePosition?.width || 0, slidePosition?.height || 0],
src: curPres?.pages[i]?.svgUri,
type: "image",
};
try {
next.pages[i + 1]["shapes"]["slide-background-shape"] = {
assetId: `slide-background-asset-${i}`,
childIndex: 1,
id: "slide-background-shape",
name: "Image",
type: TDShapeType.Image,
parentId: `${i + 1}`,
point: [0, 0],
isLocked: true,
size: [slidePosition?.width || 0, slidePosition?.height || 0],
style: {
dash: DashStyle.Draw,
size: SizeStyle.Medium,
color: ColorStyle.Blue,
},
};
} catch (err) {
logger.error({
logCode: 'whiteboard_set_slide_background_error',
extraInfo: { error: err },
}, 'Error on adding background slide image');
}
return p;
// setDoc(next);
});
}
rDocument.current = next;
const pageID = tldrawAPI?.getPage()?.id;
if (next.pageStates[pageID]?.selectedIds.length > 0) {
// if a selected id is not in the list of shapes remove it from list
next.pageStates[pageID]?.selectedIds.map((k) => {
if (!next.pages[pageID].shapes[k]) {
next.pageStates[pageID].selectedIds =
next.pageStates[pageID].selectedIds.filter(
(id) => id !== k
);
}
});
}
if (next.pageStates[pageID] && !isPresenter && !_.isEqual(slidePosition, prevSlidePosition)) {
next.pageStates[pageID].camera.point = [slidePosition.xCamera, slidePosition.yCamera]
next.pageStates[pageID].camera.zoom = slidePosition.zoom
}
setDoc(next);
if (
tldrawAPI &&
!_.isEqual(shapes, prevShapes) &&
!_.isEqual(assets, _assets)
) {
setAssets(assets);
tldrawAPI?.replacePageContent(next?.pages[pageID]?.shapes, {}, assets);
}
if (tldrawAPI && !_.isEqual(shapes, prevShapes) && !_.isEqual(assets, _assets)) {
tldrawAPI?.replacePageContent(next?.pages[pageID]?.shapes, {}, assets);
}
}, [assets, shapes, curPres, tldrawAPI, curPageId]);
return currentDoc;
}, [assets, shapes, tldrawAPI, curPageId, slidePosition]);
// change tldraw page when presentation page changes
React.useEffect(() => {
isPresenter && curPage && changeCurrentSlide(curPage?.id);
}, [curPage]);
const previousPageZoom = tldrawAPI?.getPageState()?.camera?.zoom;
tldrawAPI &&
curPageId &&
tldrawAPI.changePage(curPageId)
//change zoom of the new page to follow the previous one
previousPageZoom &&
tldrawAPI.zoomTo(previousPageZoom)
}, [curPageId]);
// change tldraw camera when slidePosition changes
React.useEffect(() => {
tldrawAPI &&
!isPresenter &&
curSlide?.activeSlide &&
tldrawAPI.changePage(curSlide?.activeSlide);
}, [curSlide]);
!isPresenter &&
curPageId &&
slidePosition &&
tldrawAPI?.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom);
}, [curPageId, slidePosition]);
const hasWBAccess = props?.hasMultiUserAccess(props.whiteboardId, props.currentUser.userId);
@ -202,47 +160,58 @@ export default function Whiteboard(props) {
<Tldraw
document={doc}
disableAssets={false}
onChangePage={(app, s, b, a) => {
setCurPage(app.getPage());
}}
onMount={(app) => {
setTLDrawAPI(app);
props.setTldrawAPI(app);
curPageId && app.changePage(curPageId);
curPageId && app.setCamera([slidePosition.xCamera, slidePosition.yCamera], slidePosition.zoom)
}}
onChange={handleChange}
//onChange={handleChange}
onPersist={(e) => {
///////////// handle assets /////////////////////////
e?.assets?.forEach((a) => {
persistAsset(a);
//persistAsset(a);
});
}}
showPages={false}
showZoom={false}
showUI={isPresenter || hasWBAccess}
showMenu={false}
showUI={curPres ? (isPresenter || hasWBAccess) : true}
showMenu={curPres ? false : true}
showMultiplayerMenu={false}
readOnly={!isPresenter && !hasWBAccess}
onUndo={s => {
s?.selectedIds?.map(id => {
persistShape(s.getShape(id), whiteboardId);
onUndo={(e, s) => {
e?.selectedIds?.map(id => {
persistShape(e.getShape(id), whiteboardId);
})
const pageShapes = e.state.document.pages[e.getPage()?.id]?.shapes;
let shapesIdsToRemove = findRemoved(Object.keys(shapes), Object.keys(pageShapes))
removeShapes(shapesIdsToRemove, whiteboardId)
}}
onRedo={(e, s) => {
e?.selectedIds?.map(id => {
persistShape(e.getShape(id), whiteboardId);
});
const pageShapes = e.state.document.pages[e.getPage()?.id]?.shapes;
let shapesIdsToRemove = findRemoved(Object.keys(shapes), Object.keys(pageShapes))
removeShapes(shapesIdsToRemove, whiteboardId)
let shapeIdsToReAdd = findRemoved(Object.keys(pageShapes), Object.keys(shapes))
shapeIdsToReAdd.forEach(id => {
persistShape(pageShapes[id], whiteboardId);
})
}}
onRedo={s => {
s?.selectedIds?.map(id => {
persistShape(s.getShape(id), whiteboardId);
});
}}
onChangePage={(app, s, b, a) => {
if (curPage?.id !== app.getPage()?.id) setCurPage(app.getPage());
if (app.getPage()?.id !== curPageId) {
skipToSlide(Number.parseInt(app.getPage()?.id), podId)
}
}}
onCommand={(e, s, g) => {
if (s.includes("session:complete:DrawSession")) {
Object.entries(rDocument?.current?.pages[e.getPage()?.id]?.shapes)
Object.entries(e.state.document.pages[e.getPage()?.id]?.shapes)
.filter(([k, s]) => s?.type === 'draw')
.forEach(([k, s]) => {
if (!e.prevShapes[k] || !k.includes('slide-background')) {
if (!e.prevShapes[k] && !k.includes('slide-background')) {
persistShape(s, whiteboardId);
}
});
@ -262,39 +231,44 @@ export default function Whiteboard(props) {
//remove shapes on origin page
removeShapes(e.selectedIds, whiteboardId);
//persist shapes for destination page
const newWhiteboardId = curPres.pages.find(page => page.num === Number.parseInt(e.getPage()?.id)).id;
movedShapes.forEach(s => {
persistShape(s, whiteboardId);
persistShape(s, newWhiteboardId);
});
}
if (s?.includes("session:complete:TransformSingleSession")
|| s?.includes("session:complete:TranslateSession")
|| s?.includes("updated_shapes")
|| s?.includes("session:complete:RotateSession")) {
const conditions = [
"session:complete:TransformSingleSession", "session:complete:TranslateSession",
"session:complete:TranslateSession", "session:complete:RotateSession",
"session:complete:HandleSession", "updated_shapes", "duplicate",
"stretch", "align", "move", "create", "flip", "toggle", "group",
]
if (conditions.some(el => s?.includes(el))) {
e.selectedIds.forEach(id => {
persistShape(e.getShape(id), whiteboardId);
//checks to find any bindings assosiated with the selected shapes.
//If any, they need to be updated as well.
const pageBindings = rDocument?.current?.pages[e.getPage()?.id]?.bindings;
const pageBindings = e.state.document.pages[e.getPage()?.id]?.bindings;
const boundShapes = [];
if (pageBindings) {
Object.entries(pageBindings).map(([k,b]) => {
if (b.toId.includes(id), whiteboardId) {
boundShapes.push(rDocument?.current?.pages[e.getPage()?.id]?.shapes[b.fromId])
if (b.toId.includes(id)) {
boundShapes.push(e.state.document.pages[e.getPage()?.id]?.shapes[b.fromId])
}
})
}
//persist shape(s) that was updated by the client and any shapes bound to it.
boundShapes.forEach(bs => persistShape(bs, whiteboardId))
const children = e.getShape(id).children
//also persist children of the selected shape (grouped shapes)
children && children.forEach(c => persistShape(e.getShape(c), whiteboardId))
});
}
if (s?.includes("session:complete:EraseSession") || s?.includes("delete")) {
let shapesIdsToRemove = []
shapes.forEach(s => {
const ids = e.shapes.map(ss => ss.id);
if (!ids.includes(s.id)) shapesIdsToRemove.push(s.id);
});
const pageShapes = e.state.document.pages[e.getPage()?.id]?.shapes;
let shapesIdsToRemove = findRemoved(Object.keys(shapes), Object.keys(pageShapes))
removeShapes(shapesIdsToRemove, whiteboardId)
}
}}

View File

@ -18,7 +18,6 @@ export default withTracker(({ whiteboardId }) => {
const shapes = Service.getShapes(whiteboardId);
const assets = Service.getAssets();
const curPres = Service.getCurrentPres();
const curSlide = Service.getCurSlide();
return {
initDefaultPages: Service.initDefaultPages,
@ -27,11 +26,11 @@ export default withTracker(({ whiteboardId }) => {
isMultiUserActive: Service.isMultiUserActive,
hasMultiUserAccess: Service.hasMultiUserAccess,
changeCurrentSlide: Service.changeCurrentSlide,
curSlide,
shapes: shapes,
assets: assets,
curPres,
removeShapes: Service.removeShapes,
zoomSlide: PresentationToolbarService.zoomSlide,
skipToSlide: PresentationToolbarService.skipToSlide,
};
})(WhiteboardContainer);

View File

@ -1,12 +1,10 @@
import Cursor from '/imports/ui/components/cursor/service';
import Cursor, { publishCursorUpdate } from '/imports/ui/components/cursor/service';
import Users from '/imports/api/users';
import { publishCursorUpdate } from '/imports/ui/components/cursor/service';
const getCurrentCursors = (whiteboardId) => {
const selector = { whiteboardId };
const filter = {};
const cursors = Cursor.find(selector, filter).fetch();
//console.log('CURSORS!!!!!!!!! : ',cursors);
return cursors.map(cursor => {
const { userId } = cursor;
const user = Users.findOne({ userId }, { fields: { name: 1, presenter: 1, userId: 1, role: 1 } });

View File

@ -1,6 +1,5 @@
import Users from '/imports/api/users';
import Captions from "/imports/api/captions";
import Meetings from "/imports/api/meetings";
import Auth from '/imports/ui/services/auth';
import WhiteboardMultiUser from '/imports/api/whiteboard-multi-user';
import addAnnotationQuery from '/imports/api/annotations/addAnnotation';
@ -8,7 +7,6 @@ import { Slides } from '/imports/api/slides';
import { makeCall } from '/imports/ui/services/api';
import PresentationService from '/imports/ui/components/presentation/service';
import logger from '/imports/startup/client/logger';
import { isEqual } from 'lodash';
const Annotations = new Mongo.Collection(null);
@ -17,7 +15,6 @@ const ANNOTATION_CONFIG = Meteor.settings.public.whiteboard.annotations;
const DRAW_START = ANNOTATION_CONFIG.status.start;
const DRAW_UPDATE = ANNOTATION_CONFIG.status.update;
const DRAW_END = ANNOTATION_CONFIG.status.end;
const ANNOTATION_TYPE_PENCIL = "pencil";
let annotationsStreamListener = null;
@ -30,63 +27,6 @@ function clearFakeAnnotations() {
Annotations.remove({ id: /-fake/g });
}
function handleAddedLiveSyncPreviewAnnotation({
meetingId, whiteboardId, userId, annotation,
}) {
const isOwn = Auth.meetingID === meetingId && Auth.userID === userId;
const query = addAnnotationQuery(meetingId, whiteboardId, userId, annotation);
if (!isOwn) {
Annotations.upsert(query.selector, query.modifier);
return;
}
const fakeAnnotation = Annotations.findOne({ id: `${annotation.id}-fake` });
let fakePoints;
if (fakeAnnotation) {
fakePoints = fakeAnnotation.annotationInfo.points;
const { points: lastPoints } = annotation.annotationInfo;
if (annotation.annotationType !== 'pencil') {
Annotations.update(fakeAnnotation._id, {
$set: {
position: annotation.position,
'annotationInfo.color': isEqual(fakePoints, lastPoints) || annotation.status === DRAW_END
? annotation.annotationInfo.color : fakeAnnotation.annotationInfo.color,
},
$inc: { version: 1 }, // TODO: Remove all this version stuff
});
return;
}
}
Annotations.upsert(query.selector, query.modifier, (err) => {
if (err) {
logger.error({
logCode: 'whiteboard_annotation_upsert_error',
extraInfo: { error: err },
}, 'Error on adding an annotation');
return;
}
// Remove fake annotation for pencil on draw end
if (annotation.status === DRAW_END) {
Annotations.remove({ id: `${annotation.id}-fake` });
return;
}
if (annotation.status === DRAW_START) {
Annotations.update(fakeAnnotation._id, {
$set: {
position: annotation.position - 1,
},
$inc: { version: 1 }, // TODO: Remove all this version stuff
});
}
});
}
function handleAddedAnnotation({
meetingId,
whiteboardId,
@ -156,20 +96,8 @@ export function initAnnotationsStreamListener() {
annotationsStreamListener.on("removed", handleRemovedAnnotation);
// <<<<<<< HEAD
// annotationsStreamListener.on('added', ({ annotations }) => {
// annotations.forEach((annotation) => {
// const tool = annotation.annotation.annotationType;
// if (tool === ANNOTATION_TYPE_TEXT) {
// handleAddedLiveSyncPreviewAnnotation(annotation);
// } else {
// handleAddedAnnotation(annotation);
// }
// });
// =======
annotationsStreamListener.on("added", ({ annotations }) => {
annotations.forEach((annotation) => handleAddedAnnotation(annotation));
// >>>>>>> embed Tldraw into BBB client
});
});
}
@ -364,7 +292,7 @@ const persistShape = (shape, whiteboardId) => {
id: shape.id,
annotationInfo: shape,
wbId: whiteboardId,
userId: Auth.userID,
userId: shape.userId ? shape.userId : Auth.userID,
};
sendAnnotation(annotation);
@ -384,11 +312,16 @@ const getShapes = (whiteboardId) => {
whiteboardId,
},
{
fields: { annotationInfo: 1, },
fields: { annotationInfo: 1, userId: 1, },
},
).fetch();
let result = annotations.map(a => a.annotationInfo);
let result = {};
annotations.forEach((annotation) => {
annotation.annotationInfo.userId = annotation.userId;
result[annotation.annotationInfo.id] = annotation.annotationInfo;
});
return result;
};
@ -397,11 +330,6 @@ const getCurrentPres = () => {
return PresentationService.getCurrentPresentation(podId);
}
const getCurSlide = () => {
let m = Meetings.findOne({ meetingId: Auth.meetingID });
return m;
}
const getAssets = () => {
// temporary storage for assets
let a = Captions.find().fetch().filter(s => s.src);

View File

@ -6325,6 +6325,11 @@
"object-assign": "^4.1.1"
}
},
"sdp": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.0.3.tgz",
"integrity": "sha512-8EkfckS+XZQaPLyChu4ey7PghrdcraCVNpJe2Gfdi2ON1ylQ7OasuKX+b37R9slnRChwIAiQgt+oj8xXGD8x+A=="
},
"sdp-transform": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.7.0.tgz",
@ -7019,6 +7024,14 @@
"resolved": "https://registry.npmjs.org/wasm-check/-/wasm-check-2.0.3.tgz",
"integrity": "sha512-UbZqpDMO4TZskoVKDH3B9NqY+yJllDJX8I9lUU4nuQjBGeU57jCjjgCslP3r8xiE+yf5GTIfeGvznvubgCdbhw=="
},
"webrtc-adapter": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.1.1.tgz",
"integrity": "sha512-1yXevP7TeZGmklEXkvQVrZp3fOSJlLeXNGCA7NovQokxgP3/e2T3EVGL0eKU87S9vKppWjvRWqnJeSANEspOBg==",
"requires": {
"sdp": "^3.0.2"
}
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -88,6 +88,7 @@
"tippy.js": "^5.1.3",
"use-context-selector": "^1.3.7",
"wasm-check": "^2.0.3",
"webrtc-adapter": "^8.1.1",
"winston": "^3.7.2",
"yaml": "^1.7.2"
},

File diff suppressed because it is too large Load Diff