Merge pull request #19783 from ramonlsouza/remove-unused-cursors
refactor: remove unused cursor code
This commit is contained in:
commit
379219085d
24
bbb-graphql-actions/src/actions/presentationPublishCursor.ts
Normal file
24
bbb-graphql-actions/src/actions/presentationPublishCursor.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { RedisMessage } from '../types';
|
||||
|
||||
export default function buildRedisMessage(sessionVariables: Record<string, unknown>, input: Record<string, unknown>): RedisMessage {
|
||||
const eventName = `SendCursorPositionPubMsg`;
|
||||
|
||||
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 = {
|
||||
whiteboardId: input.whiteboardId,
|
||||
xPercent: input.xPercent,
|
||||
yPercent: input.yPercent,
|
||||
};
|
||||
|
||||
return { eventName, routing, header, body };
|
||||
}
|
@ -287,6 +287,14 @@ type Mutation {
|
||||
): Boolean
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
presentationPublishCursor(
|
||||
whiteboardId: String!
|
||||
xPercent: Float!
|
||||
yPercent: Float!
|
||||
): Boolean
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
presentationRemove(
|
||||
presentationId: String!
|
||||
|
@ -252,6 +252,12 @@ actions:
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
comment: presentationExport
|
||||
- name: presentationPublishCursor
|
||||
definition:
|
||||
kind: synchronous
|
||||
handler: '{{HASURA_BBB_GRAPHQL_ACTIONS_ADAPTER_URL}}'
|
||||
permissions:
|
||||
- role: bbb_client
|
||||
- name: presentationRemove
|
||||
definition:
|
||||
kind: synchronous
|
||||
|
@ -1 +0,0 @@
|
||||
import './methods';
|
@ -1,6 +0,0 @@
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import publishCursorUpdate from './methods/publishCursorUpdate';
|
||||
|
||||
Meteor.methods({
|
||||
publishCursorUpdate,
|
||||
});
|
@ -1,10 +0,0 @@
|
||||
import RedisPubSub from '/imports/startup/server/redis';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
export default function publishCursorUpdate(meetingId, requesterUserId, payload) {
|
||||
const REDIS_CONFIG = Meteor.settings.private.redis;
|
||||
const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
|
||||
const EVENT_NAME = 'SendCursorPositionPubMsg';
|
||||
|
||||
return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
|
||||
}
|
@ -90,6 +90,16 @@ export const PRES_ANNOTATION_SUBMIT = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const PRESENTATION_PUBLISH_CURSOR = gql`
|
||||
mutation PresentationPublishCursor($whiteboardId: String!, $xPercent: Float!, $yPercent: Float!) {
|
||||
presentationPublishCursor(
|
||||
whiteboardId: $whiteboardId,
|
||||
xPercent: $xPercent,
|
||||
yPercent: $yPercent,
|
||||
)
|
||||
}
|
||||
`;
|
||||
|
||||
export default {
|
||||
PRESENTATION_SET_ZOOM,
|
||||
PRESENTATION_SET_WRITERS,
|
||||
@ -100,4 +110,5 @@ export default {
|
||||
PRESENTATION_REMOVE,
|
||||
PRES_ANNOTATION_DELETE,
|
||||
PRES_ANNOTATION_SUBMIT,
|
||||
PRESENTATION_PUBLISH_CURSOR,
|
||||
};
|
||||
|
@ -1,14 +1,20 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import React, {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
useMemo,
|
||||
} from 'react';
|
||||
import { useSubscription, useMutation } from '@apollo/client';
|
||||
import {
|
||||
AssetRecordType,
|
||||
} from '@tldraw/tldraw';
|
||||
import { throttle } from 'radash';
|
||||
import {
|
||||
CURRENT_PRESENTATION_PAGE_SUBSCRIPTION,
|
||||
CURRENT_PAGE_ANNOTATIONS_STREAM,
|
||||
CURRENT_PAGE_WRITERS_SUBSCRIPTION,
|
||||
CURSOR_SUBSCRIPTION,
|
||||
} from './queries';
|
||||
import { CURSOR_SUBSCRIPTION } from './cursors/queries';
|
||||
import {
|
||||
initDefaultPages,
|
||||
persistShape,
|
||||
@ -17,7 +23,6 @@ import {
|
||||
toggleToolsAnimations,
|
||||
formatAnnotations,
|
||||
} from './service';
|
||||
import CursorService from './cursors/service';
|
||||
import SettingsService from '/imports/ui/services/settings';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import {
|
||||
@ -35,6 +40,7 @@ import {
|
||||
PRES_ANNOTATION_DELETE,
|
||||
PRES_ANNOTATION_SUBMIT,
|
||||
PRESENTATION_SET_PAGE,
|
||||
PRESENTATION_PUBLISH_CURSOR,
|
||||
} from '../presentation/mutations';
|
||||
|
||||
const WHITEBOARD_CONFIG = window.meetingClientSettings.public.whiteboard;
|
||||
@ -76,6 +82,7 @@ const WhiteboardContainer = (props) => {
|
||||
const [presentationSetPage] = useMutation(PRESENTATION_SET_PAGE);
|
||||
const [presentationDeleteAnnotations] = useMutation(PRES_ANNOTATION_DELETE);
|
||||
const [presentationSubmitAnnotations] = useMutation(PRES_ANNOTATION_SUBMIT);
|
||||
const [presentationPublishCursor] = useMutation(PRESENTATION_PUBLISH_CURSOR);
|
||||
|
||||
const setPresentationPage = (pageId) => {
|
||||
presentationSetPage({
|
||||
@ -131,6 +138,23 @@ const WhiteboardContainer = (props) => {
|
||||
persistShape(shape, whiteboardId, isModerator, submitAnnotations);
|
||||
};
|
||||
|
||||
const publishCursorUpdate = (payload) => {
|
||||
const { whiteboardId, xPercent, yPercent } = payload;
|
||||
|
||||
presentationPublishCursor({
|
||||
variables: {
|
||||
whiteboardId,
|
||||
xPercent,
|
||||
yPercent,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const throttledPublishCursorUpdate = useMemo(() => throttle(
|
||||
{ interval: WHITEBOARD_CONFIG.cursorInterval },
|
||||
publishCursorUpdate,
|
||||
), []);
|
||||
|
||||
const isMultiUserActive = whiteboardWriters?.length > 0;
|
||||
|
||||
const { data: currentUser } = useCurrentUser((user) => ({
|
||||
@ -282,7 +306,7 @@ const WhiteboardContainer = (props) => {
|
||||
}}
|
||||
{...props}
|
||||
meetingId={Auth.meetingID}
|
||||
publishCursorUpdate={CursorService.publishCursorUpdate}
|
||||
publishCursorUpdate={throttledPublishCursorUpdate}
|
||||
otherCursors={cursorArray}
|
||||
hideViewersCursor={meeting?.data?.lockSettings?.hideViewersCursor}
|
||||
/>
|
||||
|
@ -1,337 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
import Cursor from './cursor/component';
|
||||
import PositionLabel from './position-label/component';
|
||||
|
||||
const XS_OFFSET = 8;
|
||||
const SMALL_OFFSET = 18;
|
||||
const XL_OFFSET = 85;
|
||||
const BOTTOM_CAM_HANDLE_HEIGHT = 10;
|
||||
const PRES_TOOLBAR_HEIGHT = 35;
|
||||
|
||||
const baseName = window.meetingClientSettings.public.app.cdn + window.meetingClientSettings.public.app.basename;
|
||||
const makeCursorUrl = (filename) => `${baseName}/resources/images/whiteboard-cursor/${filename}`;
|
||||
|
||||
const TOOL_CURSORS = {
|
||||
select: 'default',
|
||||
erase: 'crosshair',
|
||||
arrow: 'crosshair',
|
||||
draw: `url('${makeCursorUrl('pencil.png')}') 2 22, default`,
|
||||
rectangle: `url('${makeCursorUrl('square.png')}'), default`,
|
||||
ellipse: `url('${makeCursorUrl('ellipse.png')}'), default`,
|
||||
triangle: `url('${makeCursorUrl('triangle.png')}'), default`,
|
||||
line: `url('${makeCursorUrl('line.png')}'), default`,
|
||||
text: `url('${makeCursorUrl('text.png')}'), default`,
|
||||
sticky: `url('${makeCursorUrl('square.png')}'), default`,
|
||||
pan: 'grab',
|
||||
grabbing: 'grabbing',
|
||||
moving: 'move',
|
||||
};
|
||||
const Cursors = (props) => {
|
||||
const cursorWrapper = React.useRef();
|
||||
const [active, setActive] = React.useState(false);
|
||||
const [pos, setPos] = React.useState({ x: 0, y: 0 });
|
||||
const {
|
||||
whiteboardId,
|
||||
otherCursors,
|
||||
currentUser,
|
||||
currentPoint,
|
||||
tldrawCamera,
|
||||
publishCursorUpdate,
|
||||
children,
|
||||
hasWBAccess,
|
||||
isMultiUserActive,
|
||||
isPanning,
|
||||
isMoving,
|
||||
currentTool,
|
||||
toggleToolsAnimations,
|
||||
whiteboardToolbarAutoHide,
|
||||
application,
|
||||
whiteboardWriters,
|
||||
} = props;
|
||||
|
||||
const [panGrabbing, setPanGrabbing] = React.useState(false);
|
||||
|
||||
const start = (event) => {
|
||||
const targetElement = event?.target;
|
||||
const className = targetElement instanceof SVGElement
|
||||
? targetElement?.className?.baseVal
|
||||
: targetElement?.className;
|
||||
const hasTlPartial = className?.includes('tl-');
|
||||
if (hasTlPartial) {
|
||||
event?.preventDefault();
|
||||
}
|
||||
if (whiteboardToolbarAutoHide) toggleToolsAnimations('fade-out', 'fade-in', application?.animations ? '.3s' : '0s');
|
||||
setActive(true);
|
||||
};
|
||||
const handleGrabbing = () => setPanGrabbing(true);
|
||||
const handleReleaseGrab = () => setPanGrabbing(false);
|
||||
|
||||
const end = () => {
|
||||
if (whiteboardId && (hasWBAccess || currentUser?.presenter)) {
|
||||
publishCursorUpdate({
|
||||
xPercent: -1.0,
|
||||
yPercent: -1.0,
|
||||
whiteboardId,
|
||||
});
|
||||
}
|
||||
if (whiteboardToolbarAutoHide) toggleToolsAnimations('fade-in', 'fade-out', application?.animations ? '3s' : '0s');
|
||||
setActive(false);
|
||||
};
|
||||
|
||||
const moved = (event) => {
|
||||
const { type, x, y } = event;
|
||||
const nav = document.getElementById('Navbar');
|
||||
const getSibling = (el) => {
|
||||
if (el?.previousSibling && !el?.previousSibling?.hasAttribute('data-test')) {
|
||||
return el?.previousSibling;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
const panel = getSibling(nav);
|
||||
const webcams = document.getElementById('cameraDock');
|
||||
const subPanel = panel && getSibling(panel);
|
||||
const camPosition = document.getElementById('layout')?.getAttribute('data-cam-position') || null;
|
||||
const sl = document.getElementById('layout')?.getAttribute('data-layout');
|
||||
const presentationContainer = document.querySelector('[data-test="presentationContainer"]');
|
||||
const presentation = document.getElementById('currentSlideText')?.parentElement;
|
||||
const banners = document.querySelectorAll('[data-test="notificationBannerBar"]');
|
||||
let yOffset = 0;
|
||||
let xOffset = 0;
|
||||
const calcPresOffset = () => {
|
||||
yOffset
|
||||
+= (parseFloat(presentationContainer?.style?.height)
|
||||
- (parseFloat(presentation?.style?.height)
|
||||
+ (currentUser.presenter ? PRES_TOOLBAR_HEIGHT : 0))
|
||||
) / 2;
|
||||
xOffset
|
||||
+= (parseFloat(presentationContainer?.style?.width)
|
||||
- parseFloat(presentation?.style?.width)
|
||||
) / 2;
|
||||
};
|
||||
// If the presentation container is the full screen element we don't
|
||||
// need any offsets
|
||||
const { webkitFullscreenElement, fullscreenElement } = document;
|
||||
const fsEl = webkitFullscreenElement || fullscreenElement;
|
||||
if (fsEl?.getAttribute('data-test') === 'presentationContainer') {
|
||||
calcPresOffset();
|
||||
return setPos({ x: x - xOffset, y: y - yOffset });
|
||||
}
|
||||
if (nav) yOffset += parseFloat(nav?.style?.height);
|
||||
if (panel) xOffset += parseFloat(panel?.style?.width);
|
||||
if (subPanel) xOffset += parseFloat(subPanel?.style?.width);
|
||||
|
||||
// offset native tldraw eraser animation container
|
||||
const overlay = document.getElementsByClassName('tl-overlay')[0];
|
||||
if (overlay) overlay.style.left = '0px';
|
||||
|
||||
if (type === 'touchmove') {
|
||||
calcPresOffset();
|
||||
if (!active) {
|
||||
setActive(true);
|
||||
}
|
||||
const newX = event?.changedTouches[0]?.clientX - xOffset;
|
||||
const newY = event?.changedTouches[0]?.clientY - yOffset;
|
||||
return setPos({ x: newX, y: newY });
|
||||
}
|
||||
|
||||
if (document?.documentElement?.dir === 'rtl') {
|
||||
xOffset = 0;
|
||||
if (presentationContainer && presentation) {
|
||||
calcPresOffset();
|
||||
}
|
||||
if (sl.includes('custom')) {
|
||||
if (webcams) {
|
||||
if (camPosition === 'contentTop' || !camPosition) {
|
||||
yOffset += (parseFloat(webcams?.style?.height || 0) + BOTTOM_CAM_HANDLE_HEIGHT);
|
||||
}
|
||||
if (camPosition === 'contentBottom') {
|
||||
yOffset -= BOTTOM_CAM_HANDLE_HEIGHT;
|
||||
}
|
||||
if (camPosition === 'contentRight') {
|
||||
xOffset += (parseFloat(webcams?.style?.width || 0) + SMALL_OFFSET);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sl?.includes('smart')) {
|
||||
if (panel || subPanel) {
|
||||
const dockPos = webcams?.getAttribute('data-position');
|
||||
if (dockPos === 'contentTop') {
|
||||
yOffset += (parseFloat(webcams?.style?.height || 0) + SMALL_OFFSET);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (webcams && sl?.includes('videoFocus')) {
|
||||
xOffset += parseFloat(nav?.style?.width);
|
||||
yOffset += (parseFloat(panel?.style?.height || 0) - XL_OFFSET);
|
||||
}
|
||||
} else {
|
||||
if (sl.includes('custom')) {
|
||||
if (webcams) {
|
||||
if (camPosition === 'contentTop' || !camPosition) {
|
||||
yOffset += (parseFloat(webcams?.style?.height) || 0) + XS_OFFSET;
|
||||
}
|
||||
if (camPosition === 'contentBottom') {
|
||||
yOffset -= BOTTOM_CAM_HANDLE_HEIGHT;
|
||||
}
|
||||
if (camPosition === 'contentLeft') {
|
||||
xOffset += (parseFloat(webcams?.style?.width) || 0) + SMALL_OFFSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sl.includes('smart')) {
|
||||
if (panel || subPanel) {
|
||||
const dockPos = webcams?.getAttribute('data-position');
|
||||
if (dockPos === 'contentLeft') {
|
||||
xOffset += (parseFloat(webcams?.style?.width || 0) + SMALL_OFFSET);
|
||||
}
|
||||
if (dockPos === 'contentTop') {
|
||||
yOffset += (parseFloat(webcams?.style?.height || 0) + SMALL_OFFSET);
|
||||
}
|
||||
}
|
||||
if (!panel && !subPanel) {
|
||||
if (webcams) {
|
||||
xOffset = parseFloat(webcams?.style?.width || 0) + SMALL_OFFSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (sl?.includes('videoFocus')) {
|
||||
if (webcams) {
|
||||
xOffset = parseFloat(subPanel?.style?.width);
|
||||
yOffset = parseFloat(panel?.style?.height);
|
||||
}
|
||||
}
|
||||
if (presentationContainer && presentation) {
|
||||
calcPresOffset();
|
||||
}
|
||||
}
|
||||
|
||||
if (banners) {
|
||||
banners.forEach((el) => {
|
||||
yOffset += parseFloat(window.getComputedStyle(el).height);
|
||||
});
|
||||
}
|
||||
|
||||
return setPos({ x: event.x - xOffset, y: event.y - yOffset });
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const currentCursor = cursorWrapper?.current;
|
||||
currentCursor?.addEventListener('mouseenter', start);
|
||||
currentCursor?.addEventListener('touchstart', start);
|
||||
currentCursor?.addEventListener('mouseleave', end);
|
||||
currentCursor?.addEventListener('mousedown', handleGrabbing);
|
||||
currentCursor?.addEventListener('mouseup', handleReleaseGrab);
|
||||
currentCursor?.addEventListener('touchend', end);
|
||||
currentCursor?.addEventListener('mousemove', moved);
|
||||
currentCursor?.addEventListener('touchmove', moved);
|
||||
|
||||
return () => {
|
||||
currentCursor?.removeEventListener('mouseenter', start);
|
||||
currentCursor?.addEventListener('touchstart', start);
|
||||
currentCursor?.removeEventListener('mouseleave', end);
|
||||
currentCursor?.removeEventListener('mousedown', handleGrabbing);
|
||||
currentCursor?.removeEventListener('mouseup', handleReleaseGrab);
|
||||
currentCursor?.removeEventListener('touchend', end);
|
||||
currentCursor?.removeEventListener('mousemove', moved);
|
||||
currentCursor?.removeEventListener('touchmove', moved);
|
||||
};
|
||||
}, [cursorWrapper, whiteboardId, currentUser?.presenter, whiteboardToolbarAutoHide]);
|
||||
|
||||
let cursorType = hasWBAccess || currentUser?.presenter ? TOOL_CURSORS[currentTool] : 'default';
|
||||
|
||||
if (isPanning) {
|
||||
if (panGrabbing) {
|
||||
cursorType = TOOL_CURSORS.grabbing;
|
||||
} else {
|
||||
cursorType = TOOL_CURSORS.pan;
|
||||
}
|
||||
}
|
||||
if (isMoving) cursorType = TOOL_CURSORS.moving;
|
||||
return (
|
||||
<span key={`cursor-wrapper-${whiteboardId}`} ref={cursorWrapper}>
|
||||
<div style={{ height: '100%', cursor: cursorType }}>
|
||||
{((active && hasWBAccess) || (active && currentUser?.presenter)) && (
|
||||
<PositionLabel
|
||||
pos={pos}
|
||||
otherCursors={otherCursors}
|
||||
currentUser={currentUser}
|
||||
currentPoint={currentPoint}
|
||||
tldrawCamera={tldrawCamera}
|
||||
publishCursorUpdate={publishCursorUpdate}
|
||||
whiteboardId={whiteboardId}
|
||||
isMultiUserActive={isMultiUserActive}
|
||||
/>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
{otherCursors
|
||||
.filter((c) => c?.xPercent && c.xPercent !== -1.0 && c?.yPercent && c.yPercent !== -1.0)
|
||||
.map((c) => {
|
||||
if (c && currentUser?.userId !== c?.userId) {
|
||||
if (c.user.presenter) {
|
||||
return (
|
||||
<Cursor
|
||||
key={`${c?.userId}`}
|
||||
name={c?.user.name}
|
||||
color="#C70039"
|
||||
x={c?.xPercent}
|
||||
y={c?.yPercent}
|
||||
tldrawCamera={tldrawCamera}
|
||||
isMultiUserActive={isMultiUserActive}
|
||||
owner
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return whiteboardWriters?.some((writer) => writer.userId === c?.userId)
|
||||
&& (
|
||||
<Cursor
|
||||
key={`${c?.userId}`}
|
||||
name={c?.user.name}
|
||||
color="#AFE1AF"
|
||||
x={c?.xPercent}
|
||||
y={c?.yPercent}
|
||||
tldrawCamera={tldrawCamera}
|
||||
isMultiUserActive={isMultiUserActive}
|
||||
owner
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
Cursors.propTypes = {
|
||||
whiteboardId: PropTypes.string,
|
||||
otherCursors: PropTypes.arrayOf(PropTypes.shape).isRequired,
|
||||
currentUser: PropTypes.shape({
|
||||
userId: PropTypes.string.isRequired,
|
||||
presenter: PropTypes.bool.isRequired,
|
||||
}).isRequired,
|
||||
currentPoint: PropTypes.arrayOf(PropTypes.number),
|
||||
tldrawCamera: PropTypes.shape({
|
||||
point: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
zoom: PropTypes.number.isRequired,
|
||||
}),
|
||||
publishCursorUpdate: PropTypes.func.isRequired,
|
||||
children: PropTypes.arrayOf(PropTypes.element).isRequired,
|
||||
isMultiUserActive: PropTypes.bool.isRequired,
|
||||
isPanning: PropTypes.bool.isRequired,
|
||||
isMoving: PropTypes.bool.isRequired,
|
||||
currentTool: PropTypes.string,
|
||||
toggleToolsAnimations: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
Cursors.defaultProps = {
|
||||
whiteboardId: undefined,
|
||||
currentPoint: undefined,
|
||||
tldrawCamera: undefined,
|
||||
currentTool: null,
|
||||
};
|
||||
|
||||
export default Cursors;
|
@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useSubscription } from '@apollo/client';
|
||||
import SettingsService from '/imports/ui/services/settings';
|
||||
import Cursors from './component';
|
||||
import Service from './service';
|
||||
import { CURSOR_SUBSCRIPTION } from './queries';
|
||||
import { omit } from 'radash';
|
||||
|
||||
const CursorsContainer = (props) => {
|
||||
const { data: cursorData } = useSubscription(CURSOR_SUBSCRIPTION);
|
||||
const { pres_page_cursor: cursorArray } = (cursorData || []);
|
||||
|
||||
if (!cursorData) return null;
|
||||
|
||||
return (
|
||||
<Cursors
|
||||
{...{
|
||||
application: SettingsService?.application,
|
||||
publishCursorUpdate: Service.publishCursorUpdate,
|
||||
otherCursors: cursorArray,
|
||||
currentPoint: props.tldrawAPI?.currentPoint,
|
||||
tldrawCamera: props.tldrawAPI?.getPageState().camera,
|
||||
}}
|
||||
{...omit(props, ['tldrawAPI'])}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
export default CursorsContainer;
|
@ -1,93 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Meteor } from 'meteor/meteor';
|
||||
|
||||
const { pointerDiameter } = window.meetingClientSettings.public.whiteboard;
|
||||
|
||||
const Cursor = (props) => {
|
||||
const {
|
||||
name,
|
||||
color,
|
||||
x,
|
||||
y,
|
||||
currentPoint,
|
||||
tldrawCamera,
|
||||
isMultiUserActive,
|
||||
owner = false,
|
||||
} = props;
|
||||
|
||||
const z = !owner ? 2 : 1;
|
||||
let _x = null;
|
||||
let _y = null;
|
||||
|
||||
if (!currentPoint) {
|
||||
_x = (x + tldrawCamera?.point[0]) * tldrawCamera?.zoom;
|
||||
_y = (y + tldrawCamera?.point[1]) * tldrawCamera?.zoom;
|
||||
}
|
||||
|
||||
const transitionStyle = owner ? { transition: 'left 0.3s ease-out, top 0.3s ease-out' } : {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
zIndex: z,
|
||||
position: 'absolute',
|
||||
left: (_x || x) - pointerDiameter / 2,
|
||||
top: (_y || y) - pointerDiameter / 2,
|
||||
width: pointerDiameter,
|
||||
height: pointerDiameter,
|
||||
borderRadius: '50%',
|
||||
background: `${color}`,
|
||||
pointerEvents: 'none',
|
||||
...transitionStyle,
|
||||
}}
|
||||
/>
|
||||
|
||||
{isMultiUserActive && (
|
||||
<div
|
||||
style={{
|
||||
zIndex: z,
|
||||
position: 'absolute',
|
||||
pointerEvents: 'none',
|
||||
left: (_x || x) + 3.75,
|
||||
top: (_y || y) + 3,
|
||||
paddingLeft: '.25rem',
|
||||
paddingRight: '.25rem',
|
||||
paddingBottom: '.1rem',
|
||||
lineHeight: '1rem',
|
||||
borderRadius: '2px',
|
||||
color: '#FFF',
|
||||
backgroundColor: color,
|
||||
border: `1px solid ${color}`,
|
||||
...transitionStyle,
|
||||
}}
|
||||
data-test="whiteboardCursorIndicator"
|
||||
>
|
||||
{name}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Cursor.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
currentPoint: PropTypes.arrayOf(PropTypes.number),
|
||||
tldrawCamera: PropTypes.shape({
|
||||
point: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
zoom: PropTypes.number.isRequired,
|
||||
}),
|
||||
isMultiUserActive: PropTypes.bool.isRequired,
|
||||
owner: PropTypes.bool,
|
||||
};
|
||||
Cursor.defaultProps = {
|
||||
owner: false,
|
||||
currentPoint: undefined,
|
||||
tldrawCamera: undefined,
|
||||
};
|
||||
|
||||
export default Cursor;
|
@ -1,93 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import Cursor from '../cursor/component';
|
||||
|
||||
const PositionLabel = (props) => {
|
||||
const {
|
||||
currentUser,
|
||||
currentPoint,
|
||||
tldrawCamera,
|
||||
publishCursorUpdate,
|
||||
whiteboardId,
|
||||
pos,
|
||||
isMultiUserActive,
|
||||
} = props;
|
||||
|
||||
const { name, color, userId } = currentUser;
|
||||
const { x, y } = pos;
|
||||
const { zoom, point: tldrawPoint } = tldrawCamera;
|
||||
|
||||
React.useEffect(() => {
|
||||
try {
|
||||
const point = [x, y];
|
||||
publishCursorUpdate({
|
||||
xPercent:
|
||||
point[0] / zoom - tldrawPoint[0],
|
||||
yPercent:
|
||||
point[1] / zoom - tldrawPoint[1],
|
||||
whiteboardId,
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({
|
||||
logCode: 'cursor_update__error',
|
||||
extraInfo: { error },
|
||||
}, 'Whiteboard catch error on cursor update');
|
||||
}
|
||||
}, [x, y, zoom, tldrawPoint]);
|
||||
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
// Disable cursor on unmount
|
||||
publishCursorUpdate({
|
||||
xPercent: -1.0,
|
||||
yPercent: -1.0,
|
||||
whiteboardId,
|
||||
});
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ position: 'absolute', height: '100%', width: '100%' }}>
|
||||
<Cursor
|
||||
key={`${userId}-label`}
|
||||
name={name}
|
||||
color={color}
|
||||
x={x}
|
||||
y={y}
|
||||
currentPoint={currentPoint}
|
||||
tldrawCamera={tldrawCamera}
|
||||
isMultiUserActive={isMultiUserActive}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
PositionLabel.propTypes = {
|
||||
currentUser: PropTypes.shape({
|
||||
name: PropTypes.string.isRequired,
|
||||
color: PropTypes.string.isRequired,
|
||||
userId: PropTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
currentPoint: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
tldrawCamera: PropTypes.shape({
|
||||
point: PropTypes.arrayOf(PropTypes.number).isRequired,
|
||||
zoom: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
publishCursorUpdate: PropTypes.func.isRequired,
|
||||
whiteboardId: PropTypes.string,
|
||||
pos: PropTypes.shape({
|
||||
x: PropTypes.number.isRequired,
|
||||
y: PropTypes.number.isRequired,
|
||||
}).isRequired,
|
||||
isMultiUserActive: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
PositionLabel.defaultProps = {
|
||||
whiteboardId: undefined,
|
||||
};
|
||||
|
||||
export default PositionLabel;
|
@ -1,20 +0,0 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CURSOR_SUBSCRIPTION = gql`subscription CursorSubscription {
|
||||
pres_page_cursor {
|
||||
isCurrentPage
|
||||
lastUpdatedAt
|
||||
pageId
|
||||
presentationId
|
||||
userId
|
||||
xPercent
|
||||
yPercent
|
||||
user {
|
||||
name
|
||||
presenter
|
||||
role
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export default CURSOR_SUBSCRIPTION;
|
@ -1,14 +0,0 @@
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { throttle } from '/imports/utils/throttle';
|
||||
import { makeCall } from '/imports/ui/services/api';
|
||||
|
||||
const { cursorInterval: CURSOR_INTERVAL } = window.meetingClientSettings.public.whiteboard;
|
||||
|
||||
const publishCursorUpdate = throttle(
|
||||
(payload) => makeCall('publishCursorUpdate', Auth.meetingID, Auth.userID, payload),
|
||||
CURSOR_INTERVAL,
|
||||
);
|
||||
|
||||
export default {
|
||||
publishCursorUpdate,
|
||||
};
|
@ -13,9 +13,9 @@ const useCursor = (publishCursorUpdate, whiteboardId) => {
|
||||
|
||||
useEffect(() => {
|
||||
publishCursorUpdate({
|
||||
whiteboardId,
|
||||
xPercent: cursorPosition?.x,
|
||||
yPercent: cursorPosition?.y,
|
||||
whiteboardId,
|
||||
});
|
||||
}, [cursorPosition, publishCursorUpdate, whiteboardId]);
|
||||
|
||||
|
@ -112,4 +112,21 @@ export const CURRENT_PAGE_WRITERS_QUERY = gql`query currentPageWritersQuery {
|
||||
}
|
||||
}`;
|
||||
|
||||
export const CURSOR_SUBSCRIPTION = gql`subscription CursorSubscription {
|
||||
pres_page_cursor {
|
||||
isCurrentPage
|
||||
lastUpdatedAt
|
||||
pageId
|
||||
presentationId
|
||||
userId
|
||||
xPercent
|
||||
yPercent
|
||||
user {
|
||||
name
|
||||
presenter
|
||||
role
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export default CURRENT_PAGE_ANNOTATIONS_QUERY;
|
||||
|
@ -287,4 +287,4 @@ export {
|
||||
notifyShapeNumberExceeded,
|
||||
toggleToolsAnimations,
|
||||
formatAnnotations,
|
||||
};
|
||||
};
|
||||
|
@ -3,7 +3,6 @@ import '/imports/startup/server';
|
||||
// 2x
|
||||
import '/imports/api/meetings/server';
|
||||
import '/imports/api/users/server';
|
||||
import '/imports/api/cursor/server';
|
||||
import '/imports/api/polls/server';
|
||||
import '/imports/api/captions/server';
|
||||
import '/imports/api/presentation-upload-token/server';
|
||||
|
Loading…
Reference in New Issue
Block a user