Merge pull request #4734 from OZhurbenko/presenter-functionality-2x
Whiteboard touch events support, bug fixes, improvements
This commit is contained in:
commit
e5060ea6b4
4
bigbluebutton-html5/client/main.html
Executable file → Normal file
4
bigbluebutton-html5/client/main.html
Executable file → Normal file
@ -11,6 +11,8 @@
|
||||
}
|
||||
|
||||
body {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
font-family: 'Source Sans Pro', Arial, sans-serif;
|
||||
font-size: 1rem; /* 16px */
|
||||
background-color: #06172A;
|
||||
@ -21,7 +23,7 @@
|
||||
}
|
||||
|
||||
#app {
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -21,15 +21,19 @@ export default function clearAnnotations(meetingId, whiteboardId, userId) {
|
||||
return Logger.error(`Removing Annotations from collection: ${err}`);
|
||||
}
|
||||
|
||||
if (!meetingId) {
|
||||
return Logger.info('Cleared Annotations (all)');
|
||||
}
|
||||
|
||||
if (userId) {
|
||||
return Logger.info(`Removed Annotations for userId=${userId} where whiteboard=${whiteboardId}`);
|
||||
return Logger.info(`Cleared Annotations for userId=${userId} where whiteboard=${whiteboardId}`);
|
||||
}
|
||||
|
||||
return Logger.info(`Removed Annotations where whiteboard=${whiteboardId}`);
|
||||
if (whiteboardId) {
|
||||
return Logger.info(`Cleared Annotations for whiteboard=${whiteboardId}`);
|
||||
}
|
||||
|
||||
if (meetingId) {
|
||||
return Logger.info(`Cleared Annotations (${meetingId})`);
|
||||
}
|
||||
|
||||
return Logger.info('Cleared Annotations (all)');
|
||||
};
|
||||
|
||||
return Annotations.remove(selector, cb);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { Component } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const propTypes = {
|
||||
@ -24,13 +24,47 @@ const propTypes = {
|
||||
* Defines the button click handler
|
||||
* @defaultValue undefined
|
||||
*/
|
||||
onClick: PropTypes.func.isRequired,
|
||||
onClick: (props, propName, componentName) => {
|
||||
if (!props.onClick && !props.onMouseDown && !props.onMouseUp) {
|
||||
return new Error('One of props \'onClick\' or \'onMouseDown\' or' +
|
||||
` 'onMouseUp' was not specified in '${componentName}'.`);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
onMouseDown: (props, propName, componentName) => {
|
||||
if (!props.onClick && !props.onMouseDown && !props.onMouseUp) {
|
||||
return new Error('One of props \'onClick\' or \'onMouseDown\' or' +
|
||||
` 'onMouseUp' was not specified in '${componentName}'.`);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
onMouseUp: (props, propName, componentName) => {
|
||||
if (!props.onClick && !props.onMouseDown && !props.onMouseUp) {
|
||||
return new Error('One of props \'onClick\' or \'onMouseDown\' or' +
|
||||
` 'onMouseUp' was not specified in '${componentName}'.`);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
onKeyPress: PropTypes.func,
|
||||
onKeyDown: PropTypes.func,
|
||||
onKeyUp: PropTypes.func,
|
||||
setRef: PropTypes.func,
|
||||
};
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
tagName: 'button',
|
||||
role: 'button',
|
||||
onClick: undefined,
|
||||
onMouseDown: undefined,
|
||||
onMouseUp: undefined,
|
||||
onKeyPress: undefined,
|
||||
onKeyDown: undefined,
|
||||
onKeyUp: undefined,
|
||||
setRef: undefined,
|
||||
};
|
||||
|
||||
/**
|
||||
@ -40,7 +74,7 @@ const defaultProps = {
|
||||
* keyboard users to comply with ARIA standards.
|
||||
*/
|
||||
|
||||
export default class ButtonBase extends Component {
|
||||
export default class ButtonBase extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
@ -60,36 +94,38 @@ export default class ButtonBase extends Component {
|
||||
if (!this.props.disabled && typeof eventHandler === 'function') {
|
||||
return eventHandler(...args);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Define Mouse Event Handlers
|
||||
internalClickHandler(event) {
|
||||
return this.validateDisabled(this.props.onClick, ...arguments);
|
||||
internalClickHandler(...args) {
|
||||
return this.validateDisabled(this.props.onClick, ...args);
|
||||
}
|
||||
|
||||
internalDoubleClickHandler(event) {
|
||||
return this.validateDisabled(this.props.onDoubleClick, ...arguments);
|
||||
internalDoubleClickHandler(...args) {
|
||||
return this.validateDisabled(this.props.onDoubleClick, ...args);
|
||||
}
|
||||
|
||||
internalMouseDownHandler(event) {
|
||||
return this.validateDisabled(this.props.onMouseDown, ...arguments);
|
||||
internalMouseDownHandler(...args) {
|
||||
return this.validateDisabled(this.props.onMouseDown, ...args);
|
||||
}
|
||||
|
||||
internalMouseUpHandler() {
|
||||
return this.validateDisabled(this.props.onMouseUp, ...arguments);
|
||||
internalMouseUpHandler(...args) {
|
||||
return this.validateDisabled(this.props.onMouseUp, ...args);
|
||||
}
|
||||
|
||||
// Define Keyboard Event Handlers
|
||||
internalKeyPressHandler() {
|
||||
return this.validateDisabled(this.props.onKeyPress, ...arguments);
|
||||
internalKeyPressHandler(...args) {
|
||||
return this.validateDisabled(this.props.onKeyPress, ...args);
|
||||
}
|
||||
|
||||
internalKeyDownHandler() {
|
||||
return this.validateDisabled(this.props.onKeyDown, ...arguments);
|
||||
internalKeyDownHandler(...args) {
|
||||
return this.validateDisabled(this.props.onKeyDown, ...args);
|
||||
}
|
||||
|
||||
internalKeyUpHandler() {
|
||||
return this.validateDisabled(this.props.onKeyUp, ...arguments);
|
||||
internalKeyUpHandler(...args) {
|
||||
return this.validateDisabled(this.props.onKeyUp, ...args);
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -111,8 +147,12 @@ export default class ButtonBase extends Component {
|
||||
delete remainingProps.onKeyDown;
|
||||
delete remainingProps.onKeyUp;
|
||||
|
||||
// Delete setRef callback if it exists
|
||||
delete remainingProps.setRef;
|
||||
|
||||
return (
|
||||
<Component
|
||||
ref={this.props.setRef}
|
||||
aria-label={this.props.label}
|
||||
aria-disabled={this.props.disabled}
|
||||
|
||||
|
@ -1,8 +1,21 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cx from 'classnames';
|
||||
import styles from '../styles';
|
||||
|
||||
const DropdownListSeparator = (props, { style, className }) => (
|
||||
<li style={style} className={cx(styles.separator, className)} />);
|
||||
const DropdownListSeparator = ({ style, className }) =>
|
||||
(
|
||||
<li style={style} className={cx(styles.separator, className)} />
|
||||
);
|
||||
|
||||
DropdownListSeparator.propTypes = {
|
||||
style: PropTypes.shape({}),
|
||||
className: PropTypes.string,
|
||||
};
|
||||
|
||||
DropdownListSeparator.defaultProps = {
|
||||
style: null,
|
||||
className: null,
|
||||
};
|
||||
|
||||
export default DropdownListSeparator;
|
||||
|
@ -31,6 +31,7 @@ $item-border-focus: $color-blue-lighter;
|
||||
display: flex;
|
||||
flex: 1 1 100%;
|
||||
height: 1px;
|
||||
min-height: 1px;
|
||||
background-color: $color-gray-lighter;
|
||||
padding: 0;
|
||||
margin-left: -($line-height-computed / 2);
|
||||
|
@ -18,6 +18,17 @@ export default class PresentationOverlay extends Component {
|
||||
// id of the setInterval()
|
||||
this.intervalId = 0;
|
||||
|
||||
// Mobile Firefox has a bug where e.preventDefault on touchstart doesn't prevent
|
||||
// onmousedown from triggering right after. Thus we have to track it manually.
|
||||
// In case if it's fixed one day - there is another issue, React one.
|
||||
// https://github.com/facebook/react/issues/9809
|
||||
// Check it to figure if you can add onTouchStart in render(), or should use raw DOM api
|
||||
this.touchStarted = false;
|
||||
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.handleTouchCancel = this.handleTouchCancel.bind(this);
|
||||
this.mouseMoveHandler = this.mouseMoveHandler.bind(this);
|
||||
this.checkCursor = this.checkCursor.bind(this);
|
||||
this.mouseEnterHandler = this.mouseEnterHandler.bind(this);
|
||||
@ -67,7 +78,77 @@ export default class PresentationOverlay extends Component {
|
||||
return point;
|
||||
}
|
||||
|
||||
|
||||
handleTouchStart(event) {
|
||||
// to prevent default behavior (scrolling) on devices (in Safari), when you draw a text box
|
||||
event.preventDefault();
|
||||
|
||||
window.addEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.addEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.addEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
|
||||
this.touchStarted = true;
|
||||
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.currentClientX = clientX;
|
||||
this.currentClientY = clientY;
|
||||
|
||||
const intervalId = setInterval(this.checkCursor, CURSOR_INTERVAL);
|
||||
this.intervalId = intervalId;
|
||||
}
|
||||
|
||||
handleTouchMove(event) {
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
|
||||
this.currentClientX = clientX;
|
||||
this.currentClientY = clientY;
|
||||
}
|
||||
|
||||
handleTouchEnd(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// touch ended, removing the interval
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = 0;
|
||||
|
||||
// resetting the touchStarted flag
|
||||
this.touchStarted = false;
|
||||
|
||||
// setting the coords to negative values and send the last message (the cursor will disappear)
|
||||
this.currentClientX = -1;
|
||||
this.currentClientY = -1;
|
||||
this.checkCursor();
|
||||
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
}
|
||||
|
||||
handleTouchCancel(event) {
|
||||
event.preventDefault();
|
||||
|
||||
// touch was cancelled, removing the interval
|
||||
clearInterval(this.intervalId);
|
||||
this.intervalId = 0;
|
||||
|
||||
// resetting the touchStarted flag
|
||||
this.touchStarted = false;
|
||||
|
||||
// setting the coords to negative values and send the last message (the cursor will disappear)
|
||||
this.currentClientX = -1;
|
||||
this.currentClientY = -1;
|
||||
this.checkCursor();
|
||||
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
}
|
||||
|
||||
mouseMoveHandler(event) {
|
||||
if (this.touchStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for the case where you change settings in one of the lists (which are displayed on the slide)
|
||||
// the mouse starts pointing to the slide right away and mouseEnter doesn't fire
|
||||
// so we call it manually here
|
||||
@ -75,11 +156,15 @@ export default class PresentationOverlay extends Component {
|
||||
this.mouseEnterHandler();
|
||||
}
|
||||
|
||||
this.currentClientX = event.nativeEvent.clientX;
|
||||
this.currentClientY = event.nativeEvent.clientY;
|
||||
this.currentClientX = event.clientX;
|
||||
this.currentClientY = event.clientY;
|
||||
}
|
||||
|
||||
mouseEnterHandler() {
|
||||
if (this.touchStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const intervalId = setInterval(this.checkCursor, CURSOR_INTERVAL);
|
||||
this.intervalId = intervalId;
|
||||
}
|
||||
@ -105,10 +190,11 @@ export default class PresentationOverlay extends Component {
|
||||
height={this.props.slideHeight}
|
||||
>
|
||||
<div
|
||||
onTouchStart={this.handleTouchStart}
|
||||
onMouseOut={this.mouseOutHandler}
|
||||
onMouseEnter={this.mouseEnterHandler}
|
||||
onMouseMove={this.mouseMoveHandler}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{ width: '100%', height: '100%', touchAction: 'none' }}
|
||||
>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
@ -55,6 +55,19 @@ export default class TextDrawComponent extends Component {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// iOS doesn't show the keyboard if the input field was focused by event NOT invoked by a user
|
||||
// by it still technically moves the focus there
|
||||
// that's why we have a separate case for iOS - we don't focus here automatically
|
||||
// but we focus on the next "tap" invoked by a user
|
||||
const iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0;
|
||||
|
||||
// unsupported Firefox condition (not iOS though) can be removed when FF 59 is released
|
||||
// see https://bugzilla.mozilla.org/show_bug.cgi?id=1409113
|
||||
const unsupportedFirefox = navigator.userAgent.indexOf('Firefox/57') !== -1
|
||||
|| navigator.userAgent.indexOf('Firefox/58') !== -1;
|
||||
|
||||
if (iOS || unsupportedFirefox) { return; }
|
||||
|
||||
if (this.props.isActive && this.props.annotation.status !== DRAW_END) {
|
||||
this.handleFocus();
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import PencilDrawListener from './pencil-draw-listener/component';
|
||||
import PanZoomDrawListener from './pan-zoom-draw-listener/component';
|
||||
|
||||
export default class WhiteboardOverlay extends Component {
|
||||
|
||||
// a function to transform a screen point to svg point
|
||||
// accepts and returns a point of type SvgPoint and an svg object
|
||||
static coordinateTransform(screenPoint, someSvgObject) {
|
||||
@ -48,11 +47,11 @@ export default class WhiteboardOverlay extends Component {
|
||||
|
||||
// this function receives an event from the mouse event attached to the window
|
||||
// it transforms the coordinate to the main svg coordinate system
|
||||
getTransformedSvgPoint(event) {
|
||||
getTransformedSvgPoint(clientX, clientY) {
|
||||
const svgObject = this.props.getSvgRef();
|
||||
const svgPoint = svgObject.createSVGPoint();
|
||||
svgPoint.x = event.clientX;
|
||||
svgPoint.y = event.clientY;
|
||||
svgPoint.x = clientX;
|
||||
svgPoint.y = clientY;
|
||||
const transformedSvgPoint = WhiteboardOverlay.coordinateTransform(svgPoint, svgObject);
|
||||
|
||||
return transformedSvgPoint;
|
||||
@ -84,10 +83,11 @@ export default class WhiteboardOverlay extends Component {
|
||||
|
||||
// this function receives a transformed svg coordinate and checks if it's not out of bounds
|
||||
checkIfOutOfBounds(point) {
|
||||
const { viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight } = this.props;
|
||||
const {
|
||||
viewBoxX, viewBoxY, viewBoxWidth, viewBoxHeight,
|
||||
} = this.props;
|
||||
|
||||
let x = point.x;
|
||||
let y = point.y;
|
||||
let { x, y } = point;
|
||||
|
||||
// set this flag to true if either x or y are out of bounds
|
||||
let shouldUnselect = false;
|
||||
@ -120,7 +120,8 @@ export default class WhiteboardOverlay extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { drawSettings,
|
||||
const {
|
||||
drawSettings,
|
||||
userId,
|
||||
whiteboardId,
|
||||
sendAnnotation,
|
||||
|
@ -22,8 +22,13 @@ export default class PencilDrawListener extends Component {
|
||||
this.mouseDownHandler = this.mouseDownHandler.bind(this);
|
||||
this.mouseMoveHandler = this.mouseMoveHandler.bind(this);
|
||||
this.mouseUpHandler = this.mouseUpHandler.bind(this);
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.sendLastMessage = this.sendLastMessage.bind(this);
|
||||
this.sendCoordinates = this.sendCoordinates.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.handleTouchCancel = this.handleTouchCancel.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -38,11 +43,8 @@ export default class PencilDrawListener extends Component {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
// main mouse down handler
|
||||
mouseDownHandler(event) {
|
||||
if (!this.isDrawing) {
|
||||
window.addEventListener('mouseup', this.mouseUpHandler);
|
||||
window.addEventListener('mousemove', this.mouseMoveHandler, true);
|
||||
commonDrawStartHandler(clientX, clientY) {
|
||||
// changing isDrawing to true
|
||||
this.isDrawing = true;
|
||||
|
||||
const {
|
||||
@ -52,7 +54,7 @@ export default class PencilDrawListener extends Component {
|
||||
} = this.props.actions;
|
||||
|
||||
// sending the first message
|
||||
let transformedSvgPoint = getTransformedSvgPoint(event);
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// transforming svg coordinate to percentages relative to the slide width/height
|
||||
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
|
||||
@ -63,16 +65,9 @@ export default class PencilDrawListener extends Component {
|
||||
|
||||
// All the DRAW_UPDATE messages will be send on timer by sendCoordinates func
|
||||
this.intervalId = setInterval(this.sendCoordinates, MESSAGE_FREQUENCY);
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// main mouse move handler
|
||||
mouseMoveHandler(event) {
|
||||
commonDrawMoveHandler(clientX, clientY) {
|
||||
if (this.isDrawing) {
|
||||
const {
|
||||
checkIfOutOfBounds,
|
||||
@ -81,7 +76,7 @@ export default class PencilDrawListener extends Component {
|
||||
} = this.props.actions;
|
||||
|
||||
// get the transformed svg coordinate
|
||||
let transformedSvgPoint = getTransformedSvgPoint(event);
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// check if it's out of bounds
|
||||
transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint);
|
||||
@ -95,6 +90,58 @@ export default class PencilDrawListener extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchStart(event) {
|
||||
event.preventDefault();
|
||||
if (!this.isDrawing) {
|
||||
window.addEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.addEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.addEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchMove(event) {
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
handleTouchEnd() {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
handleTouchCancel() {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
// main mouse down handler
|
||||
mouseDownHandler(event) {
|
||||
if (!this.isDrawing) {
|
||||
window.addEventListener('mouseup', this.mouseUpHandler);
|
||||
window.addEventListener('mousemove', this.mouseMoveHandler, true);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
// main mouse move handler
|
||||
mouseMoveHandler(event) {
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// main mouse up handler
|
||||
mouseUpHandler() {
|
||||
this.sendLastMessage();
|
||||
@ -153,21 +200,30 @@ export default class PencilDrawListener extends Component {
|
||||
getCurrentShapeId(),
|
||||
[Math.round(physicalSlideWidth), Math.round(physicalSlideHeight)],
|
||||
);
|
||||
this.resetState();
|
||||
}
|
||||
}
|
||||
|
||||
resetState() {
|
||||
// resetting the current info
|
||||
this.points = [];
|
||||
this.isDrawing = false;
|
||||
// mouseup and mousemove are removed on desktop
|
||||
window.removeEventListener('mouseup', this.mouseUpHandler);
|
||||
window.removeEventListener('mousemove', this.mouseMoveHandler, true);
|
||||
}
|
||||
// touchend, touchmove and touchcancel are removed on devices
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div
|
||||
onTouchStart={this.handleTouchStart}
|
||||
role="presentation"
|
||||
className={styles.pencil}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{ width: '100%', height: '100%', touchAction: 'none' }}
|
||||
onMouseDown={this.mouseDownHandler}
|
||||
/>
|
||||
);
|
||||
|
@ -40,6 +40,10 @@ export default class ShapeDrawListener extends Component {
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.sendLastMessage = this.sendLastMessage.bind(this);
|
||||
this.sendCoordinates = this.sendCoordinates.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.handleTouchCancel = this.handleTouchCancel.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -54,16 +58,7 @@ export default class ShapeDrawListener extends Component {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
// main mouse down handler
|
||||
handleMouseDown(event) {
|
||||
// Sometimes when you Alt+Tab while drawing it can happen that your mouse is up,
|
||||
// but the browser didn't catch it. So check it here.
|
||||
if (this.isDrawing) {
|
||||
return this.sendLastMessage();
|
||||
}
|
||||
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
window.addEventListener('mousemove', this.handleMouseMove, true);
|
||||
commonDrawStartHandler(clientX, clientY) {
|
||||
this.isDrawing = true;
|
||||
|
||||
const {
|
||||
@ -73,7 +68,7 @@ export default class ShapeDrawListener extends Component {
|
||||
} = this.props.actions;
|
||||
|
||||
// sending the first message
|
||||
let transformedSvgPoint = getTransformedSvgPoint(event);
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// transforming svg coordinate to percentages relative to the slide width/height
|
||||
transformedSvgPoint = svgCoordinateToPercentages(transformedSvgPoint);
|
||||
@ -97,12 +92,9 @@ export default class ShapeDrawListener extends Component {
|
||||
|
||||
// All the messages will be send on timer by sendCoordinates func
|
||||
this.intervalId = setInterval(this.sendCoordinates, MESSAGE_FREQUENCY);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// main mouse move handler
|
||||
handleMouseMove(event) {
|
||||
commonDrawMoveHandler(clientX, clientY) {
|
||||
if (!this.isDrawing) {
|
||||
return;
|
||||
}
|
||||
@ -114,7 +106,7 @@ export default class ShapeDrawListener extends Component {
|
||||
} = this.props.actions;
|
||||
|
||||
// get the transformed svg coordinate
|
||||
let transformedSvgPoint = getTransformedSvgPoint(event);
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// check if it's out of bounds
|
||||
transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint);
|
||||
@ -126,6 +118,58 @@ export default class ShapeDrawListener extends Component {
|
||||
this.currentCoordinate = transformedSvgPoint;
|
||||
}
|
||||
|
||||
handleTouchStart(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (!this.isDrawing) {
|
||||
window.addEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.addEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.addEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchMove(event) {
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
handleTouchEnd() {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
handleTouchCancel() {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
// main mouse down handler
|
||||
handleMouseDown(event) {
|
||||
// Sometimes when you Alt+Tab while drawing it can happen that your mouse is up,
|
||||
// but the browser didn't catch it. So check it here.
|
||||
if (this.isDrawing) {
|
||||
return this.sendLastMessage();
|
||||
}
|
||||
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
window.addEventListener('mousemove', this.handleMouseMove, true);
|
||||
|
||||
const { clientX, clientY } = event;
|
||||
return this.commonDrawStartHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// main mouse move handler
|
||||
handleMouseMove(event) {
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// main mouse up handler
|
||||
handleMouseUp() {
|
||||
this.sendLastMessage();
|
||||
@ -190,6 +234,10 @@ export default class ShapeDrawListener extends Component {
|
||||
// resetting the current drawing state
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
// touchend, touchmove and touchcancel are removed on devices
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
this.isDrawing = false;
|
||||
this.currentStatus = undefined;
|
||||
this.initialCoordinate = {
|
||||
@ -238,12 +286,13 @@ export default class ShapeDrawListener extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const tool = this.props.drawSettings.tool;
|
||||
const { tool } = this.props.drawSettings;
|
||||
return (
|
||||
<div
|
||||
onTouchStart={this.handleTouchStart}
|
||||
role="presentation"
|
||||
className={styles[tool]}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{ width: '100%', height: '100%', touchAction: 'none' }}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
/>
|
||||
);
|
||||
|
@ -39,11 +39,23 @@ export default class TextDrawListener extends Component {
|
||||
// current text shape status, it may change between DRAW_START, DRAW_UPDATE, DRAW_END
|
||||
this.currentStatus = '';
|
||||
|
||||
// Mobile Firefox has a bug where e.preventDefault on touchstart doesn't prevent
|
||||
// onmousedown from triggering right after. Thus we have to track it manually.
|
||||
// In case if it's fixed one day - there is another issue, React one.
|
||||
// https://github.com/facebook/react/issues/9809
|
||||
// Check it to figure if you can add onTouchStart in render(), or should use raw DOM api
|
||||
this.hasBeenTouchedRecently = false;
|
||||
|
||||
this.handleMouseDown = this.handleMouseDown.bind(this);
|
||||
this.handleMouseMove = this.handleMouseMove.bind(this);
|
||||
this.handleMouseUp = this.handleMouseUp.bind(this);
|
||||
this.resetState = this.resetState.bind(this);
|
||||
this.sendLastMessage = this.sendLastMessage.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleTouchMove = this.handleTouchMove.bind(this);
|
||||
this.handleTouchEnd = this.handleTouchEnd.bind(this);
|
||||
this.handleTouchCancel = this.handleTouchCancel.bind(this);
|
||||
this.checkTextAreaFocus = this.checkTextAreaFocus.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -92,46 +104,85 @@ export default class TextDrawListener extends Component {
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('beforeunload', this.sendLastMessage);
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
|
||||
// sending the last message on componentDidUnmount
|
||||
// for example in case when you switched a tool while drawing text shape
|
||||
this.sendLastMessage();
|
||||
}
|
||||
|
||||
// checks if the input textarea is focused or not, and if not - moves focus there
|
||||
// returns false if text area wasn't focused
|
||||
// returns true if textarea was focused
|
||||
// currently used only with iOS devices
|
||||
checkTextAreaFocus() {
|
||||
const { getCurrentShapeId } = this.props.actions;
|
||||
const textarea = document.getElementById(getCurrentShapeId());
|
||||
|
||||
if (document.activeElement === textarea) {
|
||||
return true;
|
||||
}
|
||||
textarea.focus();
|
||||
return false;
|
||||
}
|
||||
|
||||
handleTouchStart(event) {
|
||||
this.hasBeenTouchedRecently = true;
|
||||
setTimeout(() => { this.hasBeenTouchedRecently = false; }, 500);
|
||||
// to prevent default behavior (scrolling) on devices (in Safari), when you draw a text box
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
// if our current drawing state is not drawing the box and not writing the text
|
||||
if (!this.state.isDrawing && !this.state.isWritingText) {
|
||||
window.addEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.addEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.addEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// this case is specifically for iOS, since text shape is working in 3 steps there:
|
||||
// touch to draw a box -> tap to focus -> tap to publish
|
||||
} else if (!this.state.isDrawing && this.state.isWritingText && !this.checkTextAreaFocus()) {
|
||||
|
||||
// if you switch to a different window using Alt+Tab while mouse is down and release it
|
||||
// it wont catch mouseUp and will keep tracking the movements. Thus we need this check.
|
||||
} else {
|
||||
this.sendLastMessage();
|
||||
}
|
||||
}
|
||||
|
||||
handleTouchMove(event) {
|
||||
const { clientX, clientY } = event.changedTouches[0];
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
handleTouchEnd() {
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
this.commonDrawEndHandler();
|
||||
}
|
||||
|
||||
handleTouchCancel() {
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
this.commonDrawEndHandler();
|
||||
}
|
||||
|
||||
// main mouse down handler
|
||||
handleMouseDown(event) {
|
||||
this.mouseDownText(event);
|
||||
if (this.hasBeenTouchedRecently) {
|
||||
return;
|
||||
}
|
||||
|
||||
// main mouse up handler
|
||||
handleMouseUp(event) {
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
this.mouseUpText(event);
|
||||
}
|
||||
|
||||
// main mouse move handler
|
||||
handleMouseMove(event) {
|
||||
this.mouseMoveText(event);
|
||||
}
|
||||
|
||||
mouseDownText(event) {
|
||||
// if our current drawing state is not drawing the box and not writing the text
|
||||
if (!this.state.isDrawing && !this.state.isWritingText) {
|
||||
window.addEventListener('mouseup', this.handleMouseUp);
|
||||
window.addEventListener('mousemove', this.handleMouseMove, true);
|
||||
|
||||
// saving initial X and Y coordinates for further displaying of the textarea
|
||||
this.initialX = event.nativeEvent.offsetX;
|
||||
this.initialY = event.nativeEvent.offsetY;
|
||||
|
||||
this.setState({
|
||||
textBoxX: event.nativeEvent.offsetX,
|
||||
textBoxY: event.nativeEvent.offsetY,
|
||||
isDrawing: true,
|
||||
});
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawStartHandler(clientX, clientY);
|
||||
|
||||
// second case is when a user finished writing the text and publishes the final result
|
||||
} else {
|
||||
@ -140,6 +191,35 @@ export default class TextDrawListener extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
// main mouse move handler
|
||||
handleMouseMove(event) {
|
||||
const { clientX, clientY } = event;
|
||||
this.commonDrawMoveHandler(clientX, clientY);
|
||||
}
|
||||
|
||||
// main mouse up handler
|
||||
handleMouseUp() {
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
this.commonDrawEndHandler();
|
||||
}
|
||||
|
||||
commonDrawStartHandler(clientX, clientY) {
|
||||
const { getTransformedSvgPoint } = this.props.actions;
|
||||
|
||||
const transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// saving initial X and Y coordinates for further displaying of the textarea
|
||||
this.initialX = transformedSvgPoint.x;
|
||||
this.initialY = transformedSvgPoint.y;
|
||||
|
||||
this.setState({
|
||||
textBoxX: transformedSvgPoint.x,
|
||||
textBoxY: transformedSvgPoint.y,
|
||||
isDrawing: true,
|
||||
});
|
||||
}
|
||||
|
||||
sendLastMessage() {
|
||||
if (!this.state.isWritingText) {
|
||||
return;
|
||||
@ -161,6 +241,14 @@ export default class TextDrawListener extends Component {
|
||||
}
|
||||
|
||||
resetState() {
|
||||
// resetting the current drawing state
|
||||
window.removeEventListener('mouseup', this.handleMouseUp);
|
||||
window.removeEventListener('mousemove', this.handleMouseMove, true);
|
||||
// touchend, touchmove and touchcancel are removed on devices
|
||||
window.removeEventListener('touchend', this.handleTouchEnd, { passive: false });
|
||||
window.removeEventListener('touchmove', this.handleTouchMove, { passive: false });
|
||||
window.removeEventListener('touchcancel', this.handleTouchCancel, true);
|
||||
|
||||
// resetting the text shape session values
|
||||
this.props.actions.resetTextShapeSession();
|
||||
// resetting the current state
|
||||
@ -182,11 +270,11 @@ export default class TextDrawListener extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
mouseMoveText(event) {
|
||||
commonDrawMoveHandler(clientX, clientY) {
|
||||
const { checkIfOutOfBounds, getTransformedSvgPoint } = this.props.actions;
|
||||
|
||||
// get the transformed svg coordinate
|
||||
let transformedSvgPoint = getTransformedSvgPoint(event);
|
||||
let transformedSvgPoint = getTransformedSvgPoint(clientX, clientY);
|
||||
|
||||
// check if it's out of bounds
|
||||
transformedSvgPoint = checkIfOutOfBounds(transformedSvgPoint);
|
||||
@ -210,13 +298,14 @@ export default class TextDrawListener extends Component {
|
||||
}
|
||||
|
||||
|
||||
mouseUpText() {
|
||||
commonDrawEndHandler() {
|
||||
// TODO - find if the size is large enough to display the text area
|
||||
if (!this.state.isDrawing && this.state.isWritingText) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { generateNewShapeId,
|
||||
const {
|
||||
generateNewShapeId,
|
||||
getCurrentShapeId,
|
||||
setTextShapeActiveId,
|
||||
} = this.props.actions;
|
||||
@ -284,8 +373,9 @@ export default class TextDrawListener extends Component {
|
||||
<div
|
||||
role="presentation"
|
||||
className={styles.text}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
style={{ width: '100%', height: '100%', touchAction: 'none' }}
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onTouchStart={this.handleTouchStart}
|
||||
>
|
||||
{this.state.isDrawing ?
|
||||
<svg
|
||||
|
@ -1,16 +1,51 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import _ from 'lodash';
|
||||
import styles from '../styles';
|
||||
|
||||
export default class ToolbarMenuItem extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.handleItemClick = this.handleItemClick.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleOnMouseUp = this.handleOnMouseUp.bind(this);
|
||||
this.setRef = this.setRef.bind(this);
|
||||
}
|
||||
|
||||
handleItemClick() {
|
||||
// generating a unique ref string for the toolbar-item
|
||||
componentWillMount() {
|
||||
this.uniqueRef = _.uniqueId('toolbar-menu-item');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// adding and removing touchstart events can be done via standard React way
|
||||
// by passing onTouchStart={this.funcName} once they stop triggering mousedown events
|
||||
// see https://github.com/facebook/react/issues/9809
|
||||
this[this.uniqueRef].addEventListener('touchstart', this.handleTouchStart);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this[this.uniqueRef].removeEventListener('touchstart', this.handleTouchStart);
|
||||
}
|
||||
|
||||
setRef(ref) {
|
||||
this[this.uniqueRef] = ref;
|
||||
}
|
||||
|
||||
// we have to use touchStart and on mouseUp in order to be able to use the toolbar
|
||||
// with the text shape on mobile devices
|
||||
// (using the toolbar while typing text shouldn't move focus out of the textarea)
|
||||
handleTouchStart(event) {
|
||||
event.preventDefault();
|
||||
const { objectToReturn, onItemClick } = this.props;
|
||||
// if there is a submenu name, then pass it to onClick
|
||||
// if not - it's probably "Undo", "Clear All", "Multi-user", etc.
|
||||
// in the second case we'll pass undefined and it will work fine anyway
|
||||
onItemClick(objectToReturn);
|
||||
}
|
||||
|
||||
handleOnMouseUp() {
|
||||
const { objectToReturn, onItemClick } = this.props;
|
||||
// if there is a submenu name, then pass it to onClick
|
||||
// if not - it's probably "Undo", "Clear All", "Multi-user", etc.
|
||||
@ -24,14 +59,15 @@ export default class ToolbarMenuItem extends Component {
|
||||
<Button
|
||||
hideLabel
|
||||
role="button"
|
||||
color={'default'}
|
||||
size={'md'}
|
||||
color="default"
|
||||
size="md"
|
||||
label={this.props.label}
|
||||
icon={this.props.icon ? this.props.icon : null}
|
||||
customIcon={this.props.customIcon ? this.props.customIcon : null}
|
||||
onClick={this.handleItemClick}
|
||||
onMouseUp={this.handleOnMouseUp}
|
||||
onBlur={this.props.onBlur}
|
||||
className={this.props.className}
|
||||
setRef={this.setRef}
|
||||
/>
|
||||
{this.props.children}
|
||||
</div>
|
||||
|
@ -1,20 +1,51 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Button from '/imports/ui/components/button/component';
|
||||
import _ from 'lodash';
|
||||
import styles from '../styles';
|
||||
|
||||
export default class ToolbarSubmenuItem extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.handleItemClick = this.handleItemClick.bind(this);
|
||||
this.handleTouchStart = this.handleTouchStart.bind(this);
|
||||
this.handleOnMouseUp = this.handleOnMouseUp.bind(this);
|
||||
this.setRef = this.setRef.bind(this);
|
||||
}
|
||||
|
||||
handleItemClick() {
|
||||
// generating a unique ref string for the toolbar-item
|
||||
componentWillMount() {
|
||||
this.uniqueRef = _.uniqueId('toolbar-submenu-item');
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// adding and removing touchstart events can be done via standard React way
|
||||
// by passing onTouchStart={this.funcName} once they stop triggering mousedown events
|
||||
// see https://github.com/facebook/react/issues/9809
|
||||
this[this.uniqueRef].addEventListener('touchstart', this.handleTouchStart);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this[this.uniqueRef].removeEventListener('touchstart', this.handleTouchStart);
|
||||
}
|
||||
|
||||
setRef(ref) {
|
||||
this[this.uniqueRef] = ref;
|
||||
}
|
||||
|
||||
// we have to use touchStart and on mouseUp in order to be able to use the toolbar
|
||||
// with the text shape on mobile devices
|
||||
// (using the toolbar while typing text shouldn't move focus out of the textarea)
|
||||
handleTouchStart(event) {
|
||||
event.preventDefault();
|
||||
const { objectToReturn, onItemClick } = this.props;
|
||||
// if there is a submenu name, then pass it to onClick
|
||||
// if not - it's probably "Undo", "Clear All", "Multi-user", etc.
|
||||
// in the second case we'll pass undefined and it will work fine anyway
|
||||
// returning the selected object
|
||||
onItemClick(objectToReturn);
|
||||
}
|
||||
|
||||
handleOnMouseUp() {
|
||||
const { objectToReturn, onItemClick } = this.props;
|
||||
// returning the selected object
|
||||
onItemClick(objectToReturn);
|
||||
}
|
||||
|
||||
@ -24,13 +55,14 @@ export default class ToolbarSubmenuItem extends Component {
|
||||
<Button
|
||||
hideLabel
|
||||
role="button"
|
||||
color={'default'}
|
||||
size={'md'}
|
||||
color="default"
|
||||
size="md"
|
||||
label={this.props.label}
|
||||
icon={this.props.icon}
|
||||
customIcon={this.props.customIcon}
|
||||
onClick={this.handleItemClick}
|
||||
onMouseUp={this.handleOnMouseUp}
|
||||
className={this.props.className}
|
||||
setRef={this.setRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user