import React, { useEffect, useState } from 'react'; import { useSubscription, useMutation } from '@apollo/client'; import { CURRENT_PRESENTATION_PAGE_SUBSCRIPTION, CURRENT_PAGE_ANNOTATIONS_STREAM, CURRENT_PAGE_WRITERS_SUBSCRIPTION, } from './queries'; 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 { layoutSelect, layoutDispatch, } from '/imports/ui/components/layout/context'; import FullscreenService from '/imports/ui/components/common/fullscreen-button/service'; import deviceInfo from '/imports/utils/deviceInfo'; import Whiteboard from './component'; import POLL_RESULTS_SUBSCRIPTION from '/imports/ui/core/graphql/queries/pollResultsSubscription'; import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser'; import useMeeting from '/imports/ui/core/hooks/useMeeting'; import { AssetRecordType, } from "@tldraw/tldraw"; import { PRESENTATION_SET_ZOOM } from '../presentation/mutations'; const WHITEBOARD_CONFIG = Meteor.settings.public.whiteboard; const WhiteboardContainer = (props) => { const { intl, slidePosition, svgUri, } = props; const [annotations, setAnnotations] = useState([]); const meeting = useMeeting((m) => ({ lockSettings: m?.lockSettings, })); const { data: presentationPageData } = useSubscription(CURRENT_PRESENTATION_PAGE_SUBSCRIPTION); const { pres_page_curr: presentationPageArray } = (presentationPageData || {}); const currentPresentationPage = presentationPageArray && presentationPageArray[0]; const curPageId = currentPresentationPage?.num; const presentationId = currentPresentationPage?.presentationId; const { data: whiteboardWritersData } = useSubscription(CURRENT_PAGE_WRITERS_SUBSCRIPTION, { variables: { pageId: currentPresentationPage?.pageId }, skip: !currentPresentationPage?.pageId, }); const whiteboardWriters = whiteboardWritersData?.pres_page_writers || []; const hasWBAccess = whiteboardWriters?.some((writer) => writer.userId === Auth.userID); const [presentationSetZoom] = useMutation(PRESENTATION_SET_ZOOM); const zoomSlide = (widthRatio, heightRatio, xOffset, yOffset) => { const { pageId, num } = currentPresentationPage; presentationSetZoom({ variables: { presentationId, pageId, pageNum: num, xOffset, yOffset, widthRatio, heightRatio, }, }); }; const isMultiUserActive = whiteboardWriters?.length > 0; const { data: pollData } = useSubscription(POLL_RESULTS_SUBSCRIPTION); const pollResults = pollData?.poll[0] || null; const { data: currentUser } = useCurrentUser((user) => ({ presenter: user.presenter, isModerator: user.isModerator, userId: user.userId, })); const { data: cursorData } = useSubscription(CURSOR_SUBSCRIPTION); const { pres_page_cursor: cursorArray } = (cursorData || []); const { data: annotationStreamData } = useSubscription( CURRENT_PAGE_ANNOTATIONS_STREAM, { variables: { lastUpdatedAt: new Date(0).toISOString() }, }, ); useEffect(() => { const { pres_annotation_curr_stream: annotationStream } = annotationStreamData || {}; if (annotationStream) { const newAnnotations = []; const annotationsToBeRemoved = []; annotationStream.forEach((item) => { if (item.annotationInfo === '') { annotationsToBeRemoved.push(item.annotationId); } else { newAnnotations.push(item); } }); const currentAnnotations = annotations.filter( (annotation) => !annotationsToBeRemoved.includes(annotation.annotationId), ); setAnnotations([...currentAnnotations, ...newAnnotations]); } }, [annotationStreamData]); let shapes = {}; let bgShape = []; const pageAnnotations = annotations .filter((annotation) => annotation.pageId === currentPresentationPage?.pageId); shapes = formatAnnotations( pageAnnotations, intl, curPageId, pollResults, currentPresentationPage, ); const { isIphone } = deviceInfo; const assetId = AssetRecordType.createId(curPageId); const assets = [{ id: assetId, typeName: "asset", type: 'image', meta: {}, props: { w: currentPresentationPage?.scaledWidth, h: currentPresentationPage?.scaledHeight, src: currentPresentationPage?.svgUrl, name: "", isAnimated: false, mimeType: null, } }]; const isRTL = layoutSelect((i) => i.isRTL); const width = layoutSelect((i) => i?.output?.presentation?.width); const height = layoutSelect((i) => i?.output?.presentation?.height); const sidebarNavigationWidth = layoutSelect( (i) => i?.output?.sidebarNavigation?.width, ); const isPresenter = currentUser?.presenter; const isModerator = currentUser?.isModerator; const { maxStickyNoteLength, maxNumberOfAnnotations } = WHITEBOARD_CONFIG; const fontFamily = WHITEBOARD_CONFIG.styles.text.family; const { colorStyle, dashStyle, fillStyle, fontStyle, sizeStyle } = WHITEBOARD_CONFIG.styles; const handleToggleFullScreen = (ref) => FullscreenService.toggleFullScreen(ref); const layoutContextDispatch = layoutDispatch(); bgShape.push({ x: 1, y: 1, rotation: 0, isLocked: true, opacity: 1, meta: {}, id: `shape:BG-${curPageId}`, type: "image", props: { w: currentPresentationPage?.scaledWidth || 1, h: currentPresentationPage?.scaledHeight || 1, assetId: assetId, playing: true, url: "", crop: null, }, parentId: `page:${curPageId}`, index: "a0", typeName: "shape", }); const hasShapeAccess = (id) => { const owner = shapes[id]?.meta?.createdBy; const isBackgroundShape = id?.includes(':BG-'); const isPollsResult = shapes[id]?.id?.includes('poll-result'); const hasAccess = (!isBackgroundShape && !isPollsResult) && ((owner && owner === currentUser?.userId) || (isPresenter) || (isModerator)) || !shapes[id]; return hasAccess; }; return ( ); }; export default WhiteboardContainer;